mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-30 10:12:40 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 445eb9e6c3 |
@@ -0,0 +1,86 @@
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
name: TestDartSassV1
|
||||
env:
|
||||
GOPROXY: https://proxy.golang.org
|
||||
GO111MODULE: on
|
||||
DART_SASS_VERSION: 1.62.1
|
||||
DART_SASS_SHA_LINUX: 3574da75a7322a539034648b8ff84ff2cca162eb924d72b663d718cd3936f075
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.22.x]
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
check-latest: true
|
||||
cache: true
|
||||
cache-dependency-path: |
|
||||
**/go.sum
|
||||
**/go.mod
|
||||
- name: Install Ruby
|
||||
uses: ruby/setup-ruby@5f19ec79cedfadb78ab837f95b87734d0003c899
|
||||
with:
|
||||
ruby-version: "2.7"
|
||||
bundler-cache: true #
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Install Mage
|
||||
run: go install github.com/magefile/mage@v1.15.0
|
||||
- name: Install asciidoctor
|
||||
uses: reitzig/actions-asciidoctor@03fcc74cd74880b697950c4930c9ec8a67c69ecc
|
||||
- name: Install docutils
|
||||
run: |
|
||||
pip install docutils
|
||||
rst2html --version
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Install pandoc on Linux
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y pandoc
|
||||
- if: matrix.os == 'macos-latest'
|
||||
run: |
|
||||
brew install pandoc
|
||||
- if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
Choco-Install -PackageName pandoc
|
||||
- run: pandoc -v
|
||||
- name: Install dart-sass-embedded Linux
|
||||
run: |
|
||||
echo "Install Dart Sass version ${DART_SASS_VERSION} ..."
|
||||
curl -LJO "https://github.com/sass/dart-sass-embedded/releases/download/${DART_SASS_VERSION}/sass_embedded-${DART_SASS_VERSION}-linux-x64.tar.gz";
|
||||
echo "${DART_SASS_SHA_LINUX} sass_embedded-${DART_SASS_VERSION}-linux-x64.tar.gz" | sha256sum -c;
|
||||
tar -xvf "sass_embedded-${DART_SASS_VERSION}-linux-x64.tar.gz";
|
||||
echo "$GITHUB_WORKSPACE/sass_embedded/" >> $GITHUB_PATH
|
||||
- name: Check
|
||||
run: |
|
||||
dart-sass-embedded --version
|
||||
mage -v check;
|
||||
env:
|
||||
HUGO_BUILD_TAGS: extended
|
||||
@@ -62,7 +62,8 @@ And with [Hugo Modules], you can share content, assets, data, translations, them
|
||||
|
||||
<p> </p>
|
||||
<p float="left">
|
||||
<a href="https://www.linode.com/?utm_campaign=hugosponsor&utm_medium=banner&utm_source=hugogithub" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/linode-logo_standard_light_medium.png" width="200" alt="Linode"></a>
|
||||
<a href="https://www.linode.com/?utm_campaign=hugosponsor&utm_medium=banner&utm_source=hugogithub" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/linode-logo_standard_light_medium.png" width="200" alt="Linode"></a>
|
||||
<a href="https://cloudcannon.com/hugo-cms/?utm_campaign=HugoSponsorship&utm_source=sponsor&utm_content=gohugo" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/cloudcannon-blue.svg" width="220" alt="CloudCannon"></a>
|
||||
<p> </p>
|
||||
|
||||
## Installation
|
||||
|
||||
Vendored
+7
-31
@@ -67,7 +67,7 @@ func New(opts Options) *Cache {
|
||||
evictedIdentities := collections.NewStack[identity.Identity]()
|
||||
|
||||
onEvict := func(k, v any) {
|
||||
if !opts.Watching {
|
||||
if !opts.Running {
|
||||
return
|
||||
}
|
||||
identity.WalkIdentitiesShallow(v, func(level int, id identity.Identity) bool {
|
||||
@@ -97,7 +97,7 @@ type Options struct {
|
||||
CheckInterval time.Duration
|
||||
MaxSize int
|
||||
MinMaxSize int
|
||||
Watching bool
|
||||
Running bool
|
||||
}
|
||||
|
||||
// Options for a partition.
|
||||
@@ -385,37 +385,13 @@ type Partition[K comparable, V any] struct {
|
||||
|
||||
// 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.doGetOrCreate(key, create)
|
||||
if err != nil {
|
||||
return p.zero, err
|
||||
}
|
||||
if resource.StaleVersion(v) > 0 {
|
||||
p.c.Delete(key)
|
||||
return p.doGetOrCreate(key, create)
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (p *Partition[K, V]) doGetOrCreate(key K, create func(key K) (V, error)) (V, error) {
|
||||
v, _, err := p.c.GetOrCreate(key, create)
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (p *Partition[K, V]) GetOrCreateWitTimeout(key K, duration time.Duration, create func(key K) (V, error)) (V, error) {
|
||||
v, err := p.doGetOrCreateWitTimeout(key, duration, create)
|
||||
if err != nil {
|
||||
return p.zero, err
|
||||
}
|
||||
if resource.StaleVersion(v) > 0 {
|
||||
p.c.Delete(key)
|
||||
return p.doGetOrCreateWitTimeout(key, duration, 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]) doGetOrCreateWitTimeout(key K, duration time.Duration, create func(key K) (V, error)) (V, error) {
|
||||
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)
|
||||
|
||||
@@ -472,7 +448,7 @@ func (p *Partition[K, V]) clearOnRebuild(changeset ...identity.Identity) {
|
||||
|
||||
shouldDelete := func(key K, v V) bool {
|
||||
// We always clear elements marked as stale.
|
||||
if resource.StaleVersion(v) > 0 {
|
||||
if resource.IsStaleAny(v) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -527,8 +503,8 @@ func (p *Partition[K, V]) Keys() []K {
|
||||
|
||||
func (p *Partition[K, V]) clearStale() {
|
||||
p.c.DeleteFunc(func(key K, v V) bool {
|
||||
staleVersion := resource.StaleVersion(v)
|
||||
if staleVersion > 0 {
|
||||
isStale := resource.IsStaleAny(v)
|
||||
if isStale {
|
||||
p.trace.Log(
|
||||
logg.StringFunc(
|
||||
func() string {
|
||||
@@ -538,7 +514,7 @@ func (p *Partition[K, V]) clearStale() {
|
||||
)
|
||||
}
|
||||
|
||||
return staleVersion > 0
|
||||
return isStale
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Vendored
+6
-6
@@ -29,12 +29,12 @@ var (
|
||||
)
|
||||
|
||||
type testItem struct {
|
||||
name string
|
||||
staleVersion uint32
|
||||
name string
|
||||
isStale bool
|
||||
}
|
||||
|
||||
func (t testItem) StaleVersion() uint32 {
|
||||
return t.staleVersion
|
||||
func (t testItem) IsStale() bool {
|
||||
return t.isStale
|
||||
}
|
||||
|
||||
func (t testItem) IdentifierBase() string {
|
||||
@@ -109,7 +109,7 @@ func newTestCache(t *testing.T) *Cache {
|
||||
|
||||
p2.GetOrCreate("clearBecauseStale", func(string) (testItem, error) {
|
||||
return testItem{
|
||||
staleVersion: 32,
|
||||
isStale: true,
|
||||
}, nil
|
||||
})
|
||||
|
||||
@@ -121,7 +121,7 @@ func newTestCache(t *testing.T) *Cache {
|
||||
|
||||
p2.GetOrCreate("clearNever", func(string) (testItem, error) {
|
||||
return testItem{
|
||||
staleVersion: 0,
|
||||
isStale: false,
|
||||
}, nil
|
||||
})
|
||||
|
||||
|
||||
@@ -327,12 +327,12 @@ func (r *rootCommand) Name() string {
|
||||
}
|
||||
|
||||
func (r *rootCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
|
||||
b := newHugoBuilder(r, nil)
|
||||
|
||||
if !r.buildWatch {
|
||||
defer b.postBuild("Total", time.Now())
|
||||
defer r.timeTrack(time.Now(), "Total")
|
||||
}
|
||||
|
||||
b := newHugoBuilder(r, nil)
|
||||
|
||||
if err := b.loadConfig(cd, false); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -506,7 +506,7 @@ Complete documentation is available at https://gohugo.io/.`
|
||||
cmd.PersistentFlags().StringVar(&r.cfgDir, "configDir", "config", "config dir")
|
||||
_ = cmd.MarkFlagDirname("configDir")
|
||||
cmd.PersistentFlags().BoolVar(&r.quiet, "quiet", false, "build in quiet mode")
|
||||
cmd.PersistentFlags().BoolVarP(&r.renderToMemory, "renderToMemory", "M", false, "render to memory (mostly useful when running the server)")
|
||||
cmd.PersistentFlags().BoolVar(&r.renderToMemory, "renderToMemory", false, "render to memory (mostly useful when running the server)")
|
||||
|
||||
cmd.PersistentFlags().BoolVarP(&r.verbose, "verbose", "v", false, "verbose output")
|
||||
cmd.PersistentFlags().BoolVarP(&r.debug, "debug", "", false, "debug output")
|
||||
|
||||
+3
-15
@@ -75,14 +75,9 @@ type hugoBuilder struct {
|
||||
errState hugoBuilderErrState
|
||||
}
|
||||
|
||||
var errConfigNotSet = errors.New("config not set")
|
||||
|
||||
func (c *hugoBuilder) withConfE(fn func(conf *commonConfig) error) error {
|
||||
c.confmu.Lock()
|
||||
defer c.confmu.Unlock()
|
||||
if c.conf == nil {
|
||||
return errConfigNotSet
|
||||
}
|
||||
return fn(c.conf)
|
||||
}
|
||||
|
||||
@@ -590,7 +585,7 @@ func (c *hugoBuilder) fullRebuild(changeType string) {
|
||||
time.Sleep(2 * time.Second)
|
||||
}()
|
||||
|
||||
defer c.postBuild("Rebuilt", time.Now())
|
||||
defer c.r.timeTrack(time.Now(), "Rebuilt")
|
||||
|
||||
err := c.reloadConfig()
|
||||
if err != nil {
|
||||
@@ -854,13 +849,13 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
|
||||
h.BaseFs.SourceFilesystems,
|
||||
dynamicEvents)
|
||||
|
||||
onePageName := pickOneWriteOrCreatePath(h.Conf.ContentTypes(), partitionedEvents.ContentEvents)
|
||||
onePageName := pickOneWriteOrCreatePath(partitionedEvents.ContentEvents)
|
||||
|
||||
c.printChangeDetected("")
|
||||
c.changeDetector.PrepareNew()
|
||||
|
||||
func() {
|
||||
defer c.postBuild("Total", time.Now())
|
||||
defer c.r.timeTrack(time.Now(), "Total")
|
||||
if err := c.rebuildSites(dynamicEvents); err != nil {
|
||||
c.handleBuildErr(err, "Rebuild failed")
|
||||
}
|
||||
@@ -906,13 +901,6 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *hugoBuilder) postBuild(what string, start time.Time) {
|
||||
if h, err := c.hugo(); err == nil && h.Conf.Running() {
|
||||
h.LogServerAddresses()
|
||||
}
|
||||
c.r.timeTrack(start, what)
|
||||
}
|
||||
|
||||
func (c *hugoBuilder) hugo() (*hugolib.HugoSites, error) {
|
||||
var h *hugolib.HugoSites
|
||||
if err := c.withConfE(func(conf *commonConfig) error {
|
||||
|
||||
+11
-26
@@ -41,8 +41,6 @@ func newListCommand() *listCommand {
|
||||
p.PublishDate().Format(time.RFC3339),
|
||||
strconv.FormatBool(p.Draft()),
|
||||
p.Permalink(),
|
||||
p.Kind(),
|
||||
p.Section(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,8 +67,6 @@ func newListCommand() *listCommand {
|
||||
"publishDate",
|
||||
"draft",
|
||||
"permalink",
|
||||
"kind",
|
||||
"section",
|
||||
})
|
||||
|
||||
for _, p := range h.Pages() {
|
||||
@@ -79,6 +75,9 @@ func newListCommand() *listCommand {
|
||||
if err := writer.Write(record); err != nil {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,8 +88,8 @@ func newListCommand() *listCommand {
|
||||
commands: []simplecobra.Commander{
|
||||
&simpleCommand{
|
||||
name: "drafts",
|
||||
short: "List draft content",
|
||||
long: `List draft content.`,
|
||||
short: "List all drafts",
|
||||
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() == nil {
|
||||
@@ -110,8 +109,8 @@ func newListCommand() *listCommand {
|
||||
},
|
||||
&simpleCommand{
|
||||
name: "future",
|
||||
short: "List future content",
|
||||
long: `List content with a future publication date.`,
|
||||
short: "List all posts dated in the future",
|
||||
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() == nil {
|
||||
@@ -130,8 +129,8 @@ func newListCommand() *listCommand {
|
||||
},
|
||||
&simpleCommand{
|
||||
name: "expired",
|
||||
short: "List expired content",
|
||||
long: `List content with a past expiration date.`,
|
||||
short: "List all posts already expired",
|
||||
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() == nil {
|
||||
@@ -150,8 +149,8 @@ func newListCommand() *listCommand {
|
||||
},
|
||||
&simpleCommand{
|
||||
name: "all",
|
||||
short: "List all content",
|
||||
long: `List all content including draft, future, and expired.`,
|
||||
short: "List all posts",
|
||||
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() != nil
|
||||
@@ -162,20 +161,6 @@ func newListCommand() *listCommand {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
},
|
||||
},
|
||||
&simpleCommand{
|
||||
name: "published",
|
||||
short: "List published content",
|
||||
long: `List content that is not draft, future, or expired.`,
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
shouldInclude := func(p page.Page) bool {
|
||||
return !p.Draft() && !resource.IsFuture(p) && !resource.IsExpired(p) && p.File() != nil
|
||||
}
|
||||
return list(cd, r, shouldInclude)
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -209,7 +209,7 @@ func (c *newCommand) newSiteNextStepsText(path string, format string) string {
|
||||
1. Change the current directory to ` + path + `.
|
||||
2. Create or install a theme:
|
||||
- Create a new theme with the command "hugo new theme <THEMENAME>"
|
||||
- Or, install a theme from https://themes.gohugo.io/
|
||||
- Install a theme from https://themes.gohugo.io/
|
||||
3. Edit hugo.` + format + `, setting the "theme" property to the theme name.
|
||||
4. Create new content with the command "hugo new content `)
|
||||
|
||||
|
||||
+5
-5
@@ -46,12 +46,12 @@ import (
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/common/urls"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
"github.com/gohugoio/hugo/hugolib/filesystems"
|
||||
"github.com/gohugoio/hugo/livereload"
|
||||
@@ -538,7 +538,7 @@ of a second, you will be able to save and see your changes nearly instantly.`
|
||||
cmd.Flags().BoolVar(&c.noHTTPCache, "noHTTPCache", false, "prevent HTTP caching")
|
||||
cmd.Flags().BoolVarP(&c.serverAppend, "appendPort", "", true, "append port to baseURL")
|
||||
cmd.Flags().BoolVar(&c.disableLiveReload, "disableLiveReload", false, "watch without enabling live browser reload on rebuild")
|
||||
cmd.Flags().BoolVarP(&c.navigateToChanged, "navigateToChanged", "N", false, "navigate to changed content file on live browser reload")
|
||||
cmd.Flags().BoolVar(&c.navigateToChanged, "navigateToChanged", false, "navigate to changed content file on live browser reload")
|
||||
cmd.Flags().BoolVar(&c.renderStaticToDisk, "renderStaticToDisk", false, "serve static files from disk and dynamic files from memory")
|
||||
cmd.Flags().BoolVar(&c.disableFastRender, "disableFastRender", false, "enables full re-renders on changes")
|
||||
cmd.Flags().BoolVar(&c.disableBrowserError, "disableBrowserError", false, "do not show build errors in the browser")
|
||||
@@ -1188,16 +1188,16 @@ func partitionDynamicEvents(sourceFs *filesystems.SourceFilesystems, events []fs
|
||||
return
|
||||
}
|
||||
|
||||
func pickOneWriteOrCreatePath(contentTypes config.ContentTypesProvider, events []fsnotify.Event) string {
|
||||
func pickOneWriteOrCreatePath(events []fsnotify.Event) string {
|
||||
name := ""
|
||||
|
||||
for _, ev := range events {
|
||||
if ev.Op&fsnotify.Write == fsnotify.Write || ev.Op&fsnotify.Create == fsnotify.Create {
|
||||
if contentTypes.IsIndexContentFile(ev.Name) {
|
||||
if files.IsIndexContentFile(ev.Name) {
|
||||
return ev.Name
|
||||
}
|
||||
|
||||
if contentTypes.IsContentFile(ev.Name) {
|
||||
if files.IsContentFile(ev.Name) {
|
||||
name = ev.Name
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ package hugo
|
||||
// This should be the only one.
|
||||
var CurrentVersion = Version{
|
||||
Major: 0,
|
||||
Minor: 126,
|
||||
PatchLevel: 3,
|
||||
Minor: 125,
|
||||
PatchLevel: 2,
|
||||
Suffix: "",
|
||||
}
|
||||
|
||||
@@ -27,12 +27,7 @@ func NewCache[K comparable, T any]() *Cache[K, T] {
|
||||
}
|
||||
|
||||
// Delete deletes the given key from the cache.
|
||||
// If c is nil, this method is a no-op.
|
||||
func (c *Cache[K, T]) Get(key K) (T, bool) {
|
||||
if c == nil {
|
||||
var zero T
|
||||
return zero, false
|
||||
}
|
||||
c.RLock()
|
||||
v, found := c.m[key]
|
||||
c.RUnlock()
|
||||
@@ -65,15 +60,6 @@ func (c *Cache[K, T]) Set(key K, value T) {
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
// ForEeach calls the given function for each key/value pair in the cache.
|
||||
func (c *Cache[K, T]) ForEeach(f func(K, T)) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
for k, v := range c.m {
|
||||
f(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// SliceCache is a simple thread safe cache backed by a map.
|
||||
type SliceCache[T any] struct {
|
||||
m map[string][]T
|
||||
|
||||
+25
-26
@@ -25,6 +25,8 @@ import (
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
)
|
||||
|
||||
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.
|
||||
@@ -32,9 +34,11 @@ type PathParser struct {
|
||||
|
||||
// Reports whether the given language is disabled.
|
||||
IsLangDisabled func(string) bool
|
||||
}
|
||||
|
||||
// Reports whether the given ext is a content file.
|
||||
IsContentExt func(string) bool
|
||||
// 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.
|
||||
@@ -104,6 +108,7 @@ func (pp *PathParser) parse(component, s string) (*Path, error) {
|
||||
var err error
|
||||
// Preserve the original case for titles etc.
|
||||
p.unnormalized, err = pp.doParse(component, s, pp.newPath(component))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -190,26 +195,23 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if len(p.identifiers) > 0 {
|
||||
isContentComponent := p.component == files.ComponentFolderContent || p.component == files.ComponentFolderArchetypes
|
||||
isContent := isContentComponent && pp.IsContentExt(p.Ext())
|
||||
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]
|
||||
if isContent {
|
||||
switch b {
|
||||
case "index":
|
||||
p.bundleType = PathTypeLeaf
|
||||
case "_index":
|
||||
p.bundleType = PathTypeBranch
|
||||
default:
|
||||
p.bundleType = PathTypeContentSingle
|
||||
}
|
||||
switch b {
|
||||
case "index":
|
||||
p.bundleType = PathTypeLeaf
|
||||
case "_index":
|
||||
p.bundleType = PathTypeBranch
|
||||
default:
|
||||
p.bundleType = PathTypeContentSingle
|
||||
}
|
||||
|
||||
if slashCount == 2 && p.IsLeafBundle() {
|
||||
p.posSectionHigh = 0
|
||||
}
|
||||
} else if b == files.NameContentData && files.IsContentDataExt(p.Ext()) {
|
||||
p.bundleType = PathTypeContentData
|
||||
if slashCount == 2 && p.IsLeafBundle() {
|
||||
p.posSectionHigh = 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,9 +246,6 @@ const (
|
||||
|
||||
// Branch bundles, e.g. /blog/_index.md
|
||||
PathTypeBranch
|
||||
|
||||
// Content data file, _content.gotmpl.
|
||||
PathTypeContentData
|
||||
)
|
||||
|
||||
type Path struct {
|
||||
@@ -522,6 +521,10 @@ func (p *Path) Identifiers() []string {
|
||||
return ids
|
||||
}
|
||||
|
||||
func (p *Path) IsHTML() bool {
|
||||
return files.IsHTML(p.Ext())
|
||||
}
|
||||
|
||||
func (p *Path) BundleType() PathType {
|
||||
return p.bundleType
|
||||
}
|
||||
@@ -538,10 +541,6 @@ func (p *Path) IsLeafBundle() bool {
|
||||
return p.bundleType == PathTypeLeaf
|
||||
}
|
||||
|
||||
func (p *Path) IsContentData() bool {
|
||||
return p.bundleType == PathTypeContentData
|
||||
}
|
||||
|
||||
func (p Path) ForBundleType(t PathType) *Path {
|
||||
p.bundleType = t
|
||||
return &p
|
||||
|
||||
@@ -27,9 +27,6 @@ var testParser = &PathParser{
|
||||
"no": 0,
|
||||
"en": 1,
|
||||
},
|
||||
IsContentExt: func(ext string) bool {
|
||||
return ext == "md"
|
||||
},
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
@@ -336,22 +333,6 @@ func TestParse(t *testing.T) {
|
||||
c.Assert(p.Path(), qt.Equals, "/a/b/c.txt")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Content data file gotmpl",
|
||||
"/a/b/_content.gotmpl",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Path(), qt.Equals, "/a/b/_content.gotmpl")
|
||||
c.Assert(p.Ext(), qt.Equals, "gotmpl")
|
||||
c.Assert(p.IsContentData(), qt.IsTrue)
|
||||
},
|
||||
},
|
||||
{
|
||||
"Content data file yaml",
|
||||
"/a/b/_content.yaml",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.IsContentData(), qt.IsFalse)
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
c.Run(test.name, func(c *qt.C) {
|
||||
|
||||
@@ -367,7 +367,6 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
|
||||
DisabledLanguages: disabledLangs,
|
||||
IgnoredLogs: ignoredLogIDs,
|
||||
KindOutputFormats: kindOutputFormats,
|
||||
ContentTypes: media.DefaultContentTypes.FromTypes(c.MediaTypes.Config),
|
||||
CreateTitle: helpers.GetTitleFunc(c.TitleCaseStyle),
|
||||
IsUglyURLSection: isUglyURL,
|
||||
IgnoreFile: ignoreFile,
|
||||
@@ -403,7 +402,6 @@ type ConfigCompiled struct {
|
||||
BaseURLLiveReload urls.BaseURL
|
||||
ServerInterface string
|
||||
KindOutputFormats map[string]output.Formats
|
||||
ContentTypes media.ContentTypes
|
||||
DisabledKinds map[string]bool
|
||||
DisabledLanguages map[string]bool
|
||||
IgnoredLogs map[string]bool
|
||||
@@ -761,7 +759,7 @@ func (c *Configs) Init() error {
|
||||
c.Languages = languages
|
||||
c.LanguagesDefaultFirst = languagesDefaultFirst
|
||||
|
||||
c.ContentPathParser = &paths.PathParser{LanguageIndex: languagesDefaultFirst.AsIndexSet(), IsLangDisabled: c.Base.IsLangDisabled, IsContentExt: c.Base.C.ContentTypes.IsContentSuffix}
|
||||
c.ContentPathParser = &paths.PathParser{LanguageIndex: languagesDefaultFirst.AsIndexSet(), IsLangDisabled: c.Base.IsLangDisabled}
|
||||
|
||||
c.configLangs = make([]config.AllProvider, len(c.Languages))
|
||||
for i, l := range c.LanguagesDefaultFirst {
|
||||
|
||||
@@ -84,21 +84,3 @@ logPathWarnings = true
|
||||
b.Assert(conf.PrintI18nWarnings, qt.Equals, true)
|
||||
b.Assert(conf.PrintPathWarnings, qt.Equals, true)
|
||||
}
|
||||
|
||||
func TestRedefineContentTypes(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
[mediaTypes]
|
||||
[mediaTypes."text/html"]
|
||||
suffixes = ["html", "xhtml"]
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
conf := b.H.Configs.Base
|
||||
contentTypes := conf.C.ContentTypes
|
||||
|
||||
b.Assert(contentTypes.HTML.Suffixes(), qt.DeepEquals, []string{"html", "xhtml"})
|
||||
b.Assert(contentTypes.Markdown.Suffixes(), qt.DeepEquals, []string{"md", "mdown", "markdown"})
|
||||
}
|
||||
|
||||
@@ -71,9 +71,6 @@ func (c ConfigLanguage) Environment() string {
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) IsMultihost() bool {
|
||||
if len(c.m.Languages)-len(c.config.C.DisabledLanguages) <= 1 {
|
||||
return false
|
||||
}
|
||||
return c.m.IsMultihost
|
||||
}
|
||||
|
||||
@@ -144,10 +141,6 @@ func (c ConfigLanguage) NewIdentityManager(name string) identity.Manager {
|
||||
return identity.NewManager(name)
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) ContentTypes() config.ContentTypesProvider {
|
||||
return c.config.C.ContentTypes
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
@@ -98,6 +98,10 @@ type BuildConfig struct {
|
||||
// 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).
|
||||
// Note that this was a bool <= v0.115.0.
|
||||
|
||||
@@ -41,7 +41,6 @@ type AllProvider interface {
|
||||
Dirs() CommonDirs
|
||||
Quiet() bool
|
||||
DirsBase() CommonDirs
|
||||
ContentTypes() ContentTypesProvider
|
||||
GetConfigSection(string) any
|
||||
GetConfig() any
|
||||
CanonifyURLs() bool
|
||||
@@ -76,15 +75,6 @@ type AllProvider interface {
|
||||
EnableEmoji() bool
|
||||
}
|
||||
|
||||
// We cannot import the media package as that would create a circular dependency.
|
||||
// This interface defineds a sub set of what media.ContentTypes provides.
|
||||
type ContentTypesProvider interface {
|
||||
IsContentSuffix(suffix string) bool
|
||||
IsContentFile(filename string) bool
|
||||
IsIndexContentFile(filename string) bool
|
||||
IsHTMLSuffix(suffix string) bool
|
||||
}
|
||||
|
||||
// Provider provides the configuration settings for Hugo.
|
||||
type Provider interface {
|
||||
GetString(key string) string
|
||||
|
||||
+3
-1
@@ -29,6 +29,8 @@ import (
|
||||
"github.com/gohugoio/hugo/common/hstrings"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
@@ -96,7 +98,7 @@ func NewContent(h *hugolib.HugoSites, kind, targetPath string, force bool) error
|
||||
return "", fmt.Errorf("failed to resolve %q to an archetype template", targetPath)
|
||||
}
|
||||
|
||||
if !h.Conf.ContentTypes().IsContentFile(b.targetPath) {
|
||||
if !files.IsContentFile(b.targetPath) {
|
||||
return "", fmt.Errorf("target path %q is not a known content format", b.targetPath)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ or site.Language.LanguageCode }}" dir="{{ or site.Language.LanguageDirection `ltr` }}">
|
||||
<html lang="{{ or site.Language.LanguageCode site.Language.Lang }}" dir="{{ or site.Language.LanguageDirection `ltr` }}">
|
||||
<head>
|
||||
{{ partial "head.html" . }}
|
||||
</head>
|
||||
|
||||
Vendored
+1
-1
@@ -155,7 +155,7 @@ func (d *Deps) Init() error {
|
||||
}
|
||||
|
||||
if d.MemCache == nil {
|
||||
d.MemCache = dynacache.New(dynacache.Options{Watching: d.Conf.Watching(), Log: d.Log})
|
||||
d.MemCache = dynacache.New(dynacache.Options{Running: d.Conf.Running(), Log: d.Log})
|
||||
}
|
||||
|
||||
if d.PathSpec == nil {
|
||||
|
||||
+1
-30
@@ -939,6 +939,7 @@ config:
|
||||
cacheBusters:
|
||||
- source: (postcss|tailwind)\.config\.js
|
||||
target: (css|styles|scss|sass)
|
||||
duplicateResourceFiles: false
|
||||
noJSConfigInAssets: false
|
||||
useResourceCacheWhen: fallback
|
||||
buildDrafts: false
|
||||
@@ -1064,15 +1065,6 @@ config:
|
||||
enable: false
|
||||
escapedSpace: false
|
||||
definitionList: true
|
||||
extras:
|
||||
insert:
|
||||
enable: false
|
||||
mark:
|
||||
enable: false
|
||||
subscript:
|
||||
enable: false
|
||||
superscript:
|
||||
enable: false
|
||||
footnote: true
|
||||
linkify: true
|
||||
linkifyProtocol: https
|
||||
@@ -1208,12 +1200,6 @@ config:
|
||||
delimiter: .
|
||||
suffixes:
|
||||
- webp
|
||||
text/asciidoc:
|
||||
delimiter: .
|
||||
suffixes:
|
||||
- adoc
|
||||
- asciidoc
|
||||
- ad
|
||||
text/calendar:
|
||||
delimiter: .
|
||||
suffixes:
|
||||
@@ -1230,7 +1216,6 @@ config:
|
||||
delimiter: .
|
||||
suffixes:
|
||||
- html
|
||||
- htm
|
||||
text/javascript:
|
||||
delimiter: .
|
||||
suffixes:
|
||||
@@ -1245,25 +1230,11 @@ config:
|
||||
delimiter: .
|
||||
suffixes:
|
||||
- md
|
||||
- mdown
|
||||
- markdown
|
||||
text/org:
|
||||
delimiter: .
|
||||
suffixes:
|
||||
- org
|
||||
text/pandoc:
|
||||
delimiter: .
|
||||
suffixes:
|
||||
- pandoc
|
||||
- pdc
|
||||
text/plain:
|
||||
delimiter: .
|
||||
suffixes:
|
||||
- txt
|
||||
text/rst:
|
||||
delimiter: .
|
||||
suffixes:
|
||||
- rst
|
||||
text/tsx:
|
||||
delimiter: .
|
||||
suffixes:
|
||||
|
||||
@@ -30,12 +30,11 @@ require (
|
||||
github.com/fortytw2/leaktest v1.3.0
|
||||
github.com/frankban/quicktest v1.14.6
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/getkin/kin-openapi v0.123.0
|
||||
github.com/getkin/kin-openapi v0.124.0
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/gobuffalo/flect v1.0.2
|
||||
github.com/gobwas/glob v0.2.3
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e
|
||||
github.com/gohugoio/hugo-goldmark-extensions/extras v0.1.0
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.2.0
|
||||
github.com/gohugoio/locales v0.14.0
|
||||
github.com/gohugoio/localescompressed v1.0.1
|
||||
@@ -56,7 +55,7 @@ require (
|
||||
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.2.2
|
||||
github.com/pelletier/go-toml/v2 v2.2.1
|
||||
github.com/rogpeppe/go-internal v1.12.0
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
|
||||
github.com/sanity-io/litter v1.5.5
|
||||
@@ -65,18 +64,18 @@ require (
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/fsync v0.10.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/tdewolff/minify/v2 v2.20.20
|
||||
github.com/tdewolff/parse/v2 v2.7.13
|
||||
github.com/tdewolff/minify/v2 v2.20.19
|
||||
github.com/tdewolff/parse/v2 v2.7.12
|
||||
github.com/yuin/goldmark v1.7.1
|
||||
github.com/yuin/goldmark-emoji v1.0.2
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
gocloud.dev v0.36.0
|
||||
golang.org/x/exp v0.0.0-20221031165847-c99f073a8326
|
||||
golang.org/x/image v0.16.0
|
||||
golang.org/x/image v0.15.0
|
||||
golang.org/x/mod v0.17.0
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/text v0.15.0
|
||||
golang.org/x/text v0.14.0
|
||||
golang.org/x/tools v0.20.0
|
||||
google.golang.org/api v0.152.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
@@ -143,9 +142,9 @@ require (
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
golang.org/x/oauth2 v0.15.0 // indirect
|
||||
golang.org/x/sys v0.20.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
|
||||
@@ -193,8 +193,6 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/getkin/kin-openapi v0.123.0 h1:zIik0mRwFNLyvtXK274Q6ut+dPh6nlxBp0x7mNrPhs8=
|
||||
github.com/getkin/kin-openapi v0.123.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM=
|
||||
github.com/getkin/kin-openapi v0.124.0 h1:VSFNMB9C9rTKBnQ/fpyDU8ytMTr4dWI9QovSKj9kz/M=
|
||||
github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
@@ -213,8 +211,6 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e h1:QArsSubW7eDh8APMXkByjQWvuljwPGAGQpJEFn0F0wY=
|
||||
github.com/gohugoio/go-i18n/v2 v2.1.3-0.20230805085216-e63c13218d0e/go.mod h1:3Ltoo9Banwq0gOtcOwxuHG6omk+AwsQPADyw2vQYOJQ=
|
||||
github.com/gohugoio/hugo-goldmark-extensions/extras v0.1.0 h1:YhxZNU8y2vxV6Ibr7QJzzUlpr8oHHWX/l+Q1R/a5Zao=
|
||||
github.com/gohugoio/hugo-goldmark-extensions/extras v0.1.0/go.mod h1:0cuvOnGKW7WeXA3i7qK6IS07FH1bgJ2XzOjQ7BMJYH4=
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.2.0 h1:PCtO5l++psZf48yen2LxQ3JiOXxaRC6v0594NeHvGZg=
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.2.0/go.mod h1:g9CCh+Ci2IMbPUrVJuXbBTrA+rIIx5+hDQ4EXYaQDoM=
|
||||
github.com/gohugoio/locales v0.14.0 h1:Q0gpsZwfv7ATHMbcTNepFd59H7GoykzWJIxi113XGDc=
|
||||
@@ -380,8 +376,8 @@ github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N
|
||||
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.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg=
|
||||
github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
|
||||
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
@@ -433,10 +429,10 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tdewolff/minify/v2 v2.20.20 h1:vhULb+VsW2twkplgsawAoUY957efb+EdiZ7zu5fUhhk=
|
||||
github.com/tdewolff/minify/v2 v2.20.20/go.mod h1:GYaLXFpIIwsX99apQHXfGdISUdlA98wmaoWxjT9C37k=
|
||||
github.com/tdewolff/parse/v2 v2.7.13 h1:iSiwOUkCYLNfapHoqdLcqZVgvQ0jrsao8YYKP/UJYTI=
|
||||
github.com/tdewolff/parse/v2 v2.7.13/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
|
||||
github.com/tdewolff/minify/v2 v2.20.19 h1:tX0SR0LUrIqGoLjXnkIzRSIbKJ7PaNnSENLD4CyH6Xo=
|
||||
github.com/tdewolff/minify/v2 v2.20.19/go.mod h1:ulkFoeAVWMLEyjuDz1ZIWOA31g5aWOawCFRp9R/MudM=
|
||||
github.com/tdewolff/parse/v2 v2.7.12 h1:tgavkHc2ZDEQVKy1oWxwIyh5bP4F5fEh/JmBwPP/3LQ=
|
||||
github.com/tdewolff/parse/v2 v2.7.12/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
|
||||
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=
|
||||
@@ -472,8 +468,8 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
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.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
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=
|
||||
@@ -489,8 +485,8 @@ golang.org/x/exp v0.0.0-20221031165847-c99f073a8326/go.mod h1:CxIveKay+FTh1D0yPZ
|
||||
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.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw=
|
||||
golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.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=
|
||||
@@ -551,8 +547,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
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.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
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=
|
||||
@@ -621,8 +617,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -634,8 +630,8 @@ 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.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
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=
|
||||
|
||||
+13
-10
@@ -26,7 +26,6 @@ import (
|
||||
|
||||
"github.com/gohugoio/hugo/common/hexec"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
|
||||
@@ -136,16 +135,20 @@ func (c *ContentSpec) SanitizeAnchorName(s string) string {
|
||||
}
|
||||
|
||||
func (c *ContentSpec) ResolveMarkup(in string) string {
|
||||
if c == nil {
|
||||
panic("nil ContentSpec")
|
||||
}
|
||||
in = strings.ToLower(in)
|
||||
|
||||
if mediaType, found := c.Cfg.ContentTypes().(media.ContentTypes).Types().GetBestMatch(markup.ResolveMarkup(in)); found {
|
||||
return mediaType.SubType
|
||||
switch in {
|
||||
case "md", "markdown", "mdown":
|
||||
return "markdown"
|
||||
case "html", "htm":
|
||||
return "html"
|
||||
default:
|
||||
if conv := c.Converters.Get(in); conv != nil {
|
||||
return conv.Name()
|
||||
}
|
||||
}
|
||||
|
||||
if conv := c.Converters.Get(in); conv != nil {
|
||||
return markup.ResolveMarkup(conv.Name())
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -241,7 +244,7 @@ func (c *ContentSpec) TrimShortHTML(input []byte, markup string) []byte {
|
||||
openingTag := []byte("<p>")
|
||||
closingTag := []byte("</p>")
|
||||
|
||||
if markup == media.DefaultContentTypes.AsciiDoc.SubType {
|
||||
if markup == "asciidocext" {
|
||||
openingTag = []byte("<div class=\"paragraph\">\n<p>")
|
||||
closingTag = []byte("</p>\n</div>")
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ func TestTrimShortHTML(t *testing.T) {
|
||||
{"markdown", []byte("<h2 id=`a`>b</h2>\n\n<p>c</p>"), []byte("<h2 id=`a`>b</h2>\n\n<p>c</p>")},
|
||||
// Issue 12369
|
||||
{"markdown", []byte("<div class=\"paragraph\">\n<p>foo</p>\n</div>"), []byte("<div class=\"paragraph\">\n<p>foo</p>\n</div>")},
|
||||
{"asciidoc", []byte("<div class=\"paragraph\">\n<p>foo</p>\n</div>"), []byte("foo")},
|
||||
{"asciidocext", []byte("<div class=\"paragraph\">\n<p>foo</p>\n</div>"), []byte("foo")},
|
||||
}
|
||||
|
||||
c := newTestContentSpec(nil)
|
||||
|
||||
@@ -35,9 +35,9 @@ func TestResolveMarkup(t *testing.T) {
|
||||
{"md", "markdown"},
|
||||
{"markdown", "markdown"},
|
||||
{"mdown", "markdown"},
|
||||
{"asciidocext", "asciidoc"},
|
||||
{"adoc", "asciidoc"},
|
||||
{"ad", "asciidoc"},
|
||||
{"asciidocext", "asciidocext"},
|
||||
{"adoc", "asciidocext"},
|
||||
{"ad", "asciidocext"},
|
||||
{"rst", "rst"},
|
||||
{"pandoc", "pandoc"},
|
||||
{"pdc", "pandoc"},
|
||||
|
||||
@@ -29,13 +29,57 @@ const (
|
||||
FilenameHugoStatsJSON = "hugo_stats.json"
|
||||
)
|
||||
|
||||
func IsGoTmplExt(ext string) bool {
|
||||
return ext == "gotmpl"
|
||||
var (
|
||||
// This should be the only list of valid extensions for content files.
|
||||
contentFileExtensions = []string{
|
||||
"html", "htm",
|
||||
"mdown", "markdown", "md",
|
||||
"asciidoc", "adoc", "ad",
|
||||
"rest", "rst",
|
||||
"org",
|
||||
"pandoc", "pdc",
|
||||
}
|
||||
|
||||
contentFileExtensionsSet map[string]bool
|
||||
|
||||
htmlFileExtensions = []string{
|
||||
"html", "htm",
|
||||
}
|
||||
|
||||
htmlFileExtensionsSet map[string]bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
contentFileExtensionsSet = make(map[string]bool)
|
||||
for _, ext := range contentFileExtensions {
|
||||
contentFileExtensionsSet[ext] = true
|
||||
}
|
||||
htmlFileExtensionsSet = make(map[string]bool)
|
||||
for _, ext := range htmlFileExtensions {
|
||||
htmlFileExtensionsSet[ext] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Supported data file extensions for _content.* files.
|
||||
func IsContentDataExt(ext string) bool {
|
||||
return IsGoTmplExt(ext)
|
||||
func IsContentFile(filename string) bool {
|
||||
return contentFileExtensionsSet[strings.TrimPrefix(filepath.Ext(filename), ".")]
|
||||
}
|
||||
|
||||
func IsIndexContentFile(filename string) bool {
|
||||
if !IsContentFile(filename) {
|
||||
return false
|
||||
}
|
||||
|
||||
base := filepath.Base(filename)
|
||||
|
||||
return strings.HasPrefix(base, "index.") || strings.HasPrefix(base, "_index.")
|
||||
}
|
||||
|
||||
func IsHTML(ext string) bool {
|
||||
return htmlFileExtensionsSet[ext]
|
||||
}
|
||||
|
||||
func IsContentExt(ext string) bool {
|
||||
return contentFileExtensionsSet[ext]
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -49,8 +93,6 @@ const (
|
||||
|
||||
FolderResources = "resources"
|
||||
FolderJSConfig = "_jsconfig" // Mounted below /assets with postcss.config.js etc.
|
||||
|
||||
NameContentData = "_content"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -14,11 +14,22 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestIsContentFile(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(IsContentFile(filepath.FromSlash("my/file.md")), qt.Equals, true)
|
||||
c.Assert(IsContentFile(filepath.FromSlash("my/file.ad")), qt.Equals, true)
|
||||
c.Assert(IsContentFile(filepath.FromSlash("textfile.txt")), qt.Equals, false)
|
||||
c.Assert(IsContentExt("md"), qt.Equals, true)
|
||||
c.Assert(IsContentExt("json"), qt.Equals, false)
|
||||
}
|
||||
|
||||
func TestComponentFolders(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
|
||||
+2
-8
@@ -23,7 +23,6 @@ import (
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
@@ -51,8 +50,7 @@ type WalkwayConfig struct {
|
||||
Root string
|
||||
|
||||
// The logger to use.
|
||||
Logger loggers.Logger
|
||||
PathParser *paths.PathParser
|
||||
Logger loggers.Logger
|
||||
|
||||
// One or both of these may be pre-set.
|
||||
Info FileMetaInfo // The start info.
|
||||
@@ -74,10 +72,6 @@ func NewWalkway(cfg WalkwayConfig) *Walkway {
|
||||
panic("fs must be set")
|
||||
}
|
||||
|
||||
if cfg.PathParser == nil {
|
||||
cfg.PathParser = media.DefaultPathParser
|
||||
}
|
||||
|
||||
logger := cfg.Logger
|
||||
if logger == nil {
|
||||
logger = loggers.NewDefault()
|
||||
@@ -167,7 +161,7 @@ func (w *Walkway) walk(path string, info FileMetaInfo, dirEntries []FileMetaInfo
|
||||
dirEntries = DirEntriesToFileMetaInfos(fis)
|
||||
for _, fi := range dirEntries {
|
||||
if fi.Meta().PathInfo == nil {
|
||||
fi.Meta().PathInfo = w.cfg.PathParser.Parse("", filepath.Join(pathRel, fi.Name()))
|
||||
fi.Meta().PathInfo = paths.Parse("", filepath.Join(pathRel, fi.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-50
@@ -329,7 +329,7 @@ cascade:
|
||||
|
||||
counters := &buildCounters{}
|
||||
b.Build(BuildCfg{testCounters: counters})
|
||||
b.Assert(int(counters.contentRenderCounter.Load()), qt.Equals, 1)
|
||||
b.Assert(int(counters.contentRenderCounter.Load()), qt.Equals, 2)
|
||||
|
||||
b.AssertFileContent("public/post/index.html", `Banner: post.jpg|Layout: postlayout|Type: posttype|Content: <p>content edit</p>`)
|
||||
b.AssertFileContent("public/post/dir/p1/index.html", `Banner: post.jpg|Layout: postlayout|`)
|
||||
@@ -672,55 +672,6 @@ S1|p1:|p2:p2|
|
||||
})
|
||||
}
|
||||
|
||||
func TestCascadeEditIssue12449(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableKinds = ['sitemap','rss', 'home', 'taxonomy','term']
|
||||
disableLiveReload = true
|
||||
-- layouts/_default/list.html --
|
||||
Title: {{ .Title }}|{{ .Content }}|cascadeparam: {{ .Params.cascadeparam }}|
|
||||
-- layouts/_default/single.html --
|
||||
Title: {{ .Title }}|{{ .Content }}|cascadeparam: {{ .Params.cascadeparam }}|
|
||||
-- content/mysect/_index.md --
|
||||
---
|
||||
title: mysect
|
||||
cascade:
|
||||
description: descriptionvalue
|
||||
params:
|
||||
cascadeparam: cascadeparamvalue
|
||||
---
|
||||
mysect-content|
|
||||
-- content/mysect/p1/index.md --
|
||||
---
|
||||
slug: p1
|
||||
---
|
||||
p1-content|
|
||||
-- content/mysect/subsect/_index.md --
|
||||
---
|
||||
slug: subsect
|
||||
---
|
||||
subsect-content|
|
||||
`
|
||||
|
||||
b := TestRunning(t, files)
|
||||
|
||||
// Make the cascade set the title.
|
||||
b.EditFileReplaceAll("content/mysect/_index.md", "description: descriptionvalue", "title: cascadetitle").Build()
|
||||
b.AssertFileContent("public/mysect/subsect/index.html", "Title: cascadetitle|")
|
||||
|
||||
// Edit cascade title.
|
||||
b.EditFileReplaceAll("content/mysect/_index.md", "title: cascadetitle", "title: cascadetitle-edit").Build()
|
||||
b.AssertFileContent("public/mysect/subsect/index.html", "Title: cascadetitle-edit|")
|
||||
|
||||
// Revert title change.
|
||||
// The step below failed in #12449.
|
||||
b.EditFileReplaceAll("content/mysect/_index.md", "title: cascadetitle-edit", "description: descriptionvalue").Build()
|
||||
b.AssertFileContent("public/mysect/subsect/index.html", "Title: |")
|
||||
}
|
||||
|
||||
// Issue 11977.
|
||||
func TestCascadeExtensionInPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -1144,7 +1144,7 @@ Home.
|
||||
|
||||
enConfig := b.H.Sites[0].conf
|
||||
m, _ := enConfig.MediaTypes.Config.GetByType("text/html")
|
||||
b.Assert(m.Suffixes(), qt.DeepEquals, []string{"html", "htm"})
|
||||
b.Assert(m.Suffixes(), qt.DeepEquals, []string{"html"})
|
||||
|
||||
svConfig := b.H.Sites[1].conf
|
||||
f, _ := svConfig.OutputFormats.Config.GetByName("html")
|
||||
|
||||
+18
-224
@@ -14,7 +14,6 @@
|
||||
package hugolib
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@@ -24,13 +23,10 @@ import (
|
||||
"github.com/bep/logg"
|
||||
"github.com/gohugoio/hugo/common/hugio"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
"github.com/gohugoio/hugo/hugolib/pagesfromdata"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/source"
|
||||
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
"github.com/gohugoio/hugo/resources/page/pagemeta"
|
||||
"github.com/gohugoio/hugo/resources/resource"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
@@ -55,11 +51,9 @@ type contentMapConfig struct {
|
||||
var _ contentNodeI = (*resourceSource)(nil)
|
||||
|
||||
type resourceSource struct {
|
||||
langIndex int
|
||||
path *paths.Path
|
||||
opener hugio.OpenReadSeekCloser
|
||||
fi hugofs.FileMetaInfo
|
||||
rc *pagemeta.ResourceConfig
|
||||
path *paths.Path
|
||||
opener hugio.OpenReadSeekCloser
|
||||
fi hugofs.FileMetaInfo
|
||||
|
||||
r resource.Resource
|
||||
}
|
||||
@@ -70,7 +64,11 @@ func (r resourceSource) clone() *resourceSource {
|
||||
}
|
||||
|
||||
func (r *resourceSource) LangIndex() int {
|
||||
return r.langIndex
|
||||
if r.r != nil && r.isPage() {
|
||||
return r.r.(*pageState).s.languagei
|
||||
}
|
||||
|
||||
return r.fi.Meta().LangIndex
|
||||
}
|
||||
|
||||
func (r *resourceSource) MarkStale() {
|
||||
@@ -164,62 +162,12 @@ func (cfg contentMapConfig) getTaxonomyConfig(s string) (v viewName) {
|
||||
return
|
||||
}
|
||||
|
||||
func (m *pageMap) insertPageWithLock(s string, p *pageState) (contentNodeI, contentNodeI, bool) {
|
||||
u, n, replaced := m.treePages.InsertIntoValuesDimensionWithLock(s, p)
|
||||
|
||||
if replaced && !m.s.h.isRebuild() && m.s.conf.PrintPathWarnings {
|
||||
var messageDetail string
|
||||
if p1, ok := n.(*pageState); ok && p1.File() != nil {
|
||||
messageDetail = fmt.Sprintf(" file: %q", p1.File().Filename())
|
||||
}
|
||||
if p2, ok := u.(*pageState); ok && p2.File() != nil {
|
||||
messageDetail += fmt.Sprintf(" file: %q", p2.File().Filename())
|
||||
}
|
||||
|
||||
m.s.Log.Warnf("Duplicate content path: %q%s", s, messageDetail)
|
||||
}
|
||||
|
||||
return u, n, replaced
|
||||
}
|
||||
|
||||
func (m *pageMap) insertResourceWithLock(s string, r contentNodeI) (contentNodeI, contentNodeI, bool) {
|
||||
u, n, replaced := m.treeResources.InsertIntoValuesDimensionWithLock(s, r)
|
||||
if replaced {
|
||||
m.handleDuplicateResourcePath(s, r, n)
|
||||
}
|
||||
return u, n, replaced
|
||||
}
|
||||
|
||||
func (m *pageMap) insertResource(s string, r contentNodeI) (contentNodeI, contentNodeI, bool) {
|
||||
u, n, replaced := m.treeResources.InsertIntoValuesDimension(s, r)
|
||||
if replaced {
|
||||
m.handleDuplicateResourcePath(s, r, n)
|
||||
}
|
||||
return u, n, replaced
|
||||
}
|
||||
|
||||
func (m *pageMap) handleDuplicateResourcePath(s string, updated, existing contentNodeI) {
|
||||
if m.s.h.isRebuild() || !m.s.conf.PrintPathWarnings {
|
||||
return
|
||||
}
|
||||
var messageDetail string
|
||||
if r1, ok := existing.(*resourceSource); ok && r1.fi != nil {
|
||||
messageDetail = fmt.Sprintf(" file: %q", r1.fi.Meta().Filename)
|
||||
}
|
||||
if r2, ok := updated.(*resourceSource); ok && r2.fi != nil {
|
||||
messageDetail += fmt.Sprintf(" file: %q", r2.fi.Meta().Filename)
|
||||
}
|
||||
|
||||
m.s.Log.Warnf("Duplicate resource path: %q%s", s, messageDetail)
|
||||
}
|
||||
|
||||
func (m *pageMap) AddFi(fi hugofs.FileMetaInfo, buildConfig *BuildCfg) (pageCount uint64, resourceCount uint64, addErr error) {
|
||||
func (m *pageMap) AddFi(fi hugofs.FileMetaInfo) error {
|
||||
if fi.IsDir() {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
insertResource := func(fim hugofs.FileMetaInfo) error {
|
||||
resourceCount++
|
||||
pi := fi.Meta().PathInfo
|
||||
key := pi.Base()
|
||||
tree := m.treeResources
|
||||
@@ -251,12 +199,12 @@ func (m *pageMap) AddFi(fi hugofs.FileMetaInfo, buildConfig *BuildCfg) (pageCoun
|
||||
}
|
||||
key = pi.Base()
|
||||
|
||||
rs = &resourceSource{r: pageResource, langIndex: pageResource.s.languagei}
|
||||
rs = &resourceSource{r: pageResource}
|
||||
} else {
|
||||
rs = &resourceSource{path: pi, opener: r, fi: fim, langIndex: fim.Meta().LangIndex}
|
||||
rs = &resourceSource{path: pi, opener: r, fi: fim}
|
||||
}
|
||||
|
||||
_, _, _ = m.insertResource(key, rs)
|
||||
tree.InsertIntoValuesDimension(key, rs)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -272,27 +220,14 @@ func (m *pageMap) AddFi(fi hugofs.FileMetaInfo, buildConfig *BuildCfg) (pageCoun
|
||||
},
|
||||
))
|
||||
if err := insertResource(fi); err != nil {
|
||||
addErr = err
|
||||
return
|
||||
return err
|
||||
}
|
||||
case paths.PathTypeContentData:
|
||||
pc, rc, err := m.addPagesFromGoTmplFi(fi, buildConfig)
|
||||
pageCount += pc
|
||||
resourceCount += rc
|
||||
if err != nil {
|
||||
addErr = err
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
m.s.Log.Trace(logg.StringFunc(
|
||||
func() string {
|
||||
return fmt.Sprintf("insert bundle: %q", fi.Meta().Filename)
|
||||
},
|
||||
))
|
||||
|
||||
pageCount++
|
||||
|
||||
// A content file.
|
||||
p, pi, err := m.s.h.newPage(
|
||||
&pageMeta{
|
||||
@@ -302,158 +237,17 @@ func (m *pageMap) AddFi(fi hugofs.FileMetaInfo, buildConfig *BuildCfg) (pageCoun
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
addErr = err
|
||||
return
|
||||
return err
|
||||
}
|
||||
if p == nil {
|
||||
// Disabled page.
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
m.insertPageWithLock(pi.Base(), p)
|
||||
m.treePages.InsertWithLock(pi.Base(), p)
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *pageMap) addPagesFromGoTmplFi(fi hugofs.FileMetaInfo, buildConfig *BuildCfg) (pageCount uint64, resourceCount uint64, addErr error) {
|
||||
meta := fi.Meta()
|
||||
pi := meta.PathInfo
|
||||
|
||||
m.s.Log.Trace(logg.StringFunc(
|
||||
func() string {
|
||||
return fmt.Sprintf("insert pages from data file: %q", fi.Meta().Filename)
|
||||
},
|
||||
))
|
||||
|
||||
if !files.IsGoTmplExt(pi.Ext()) {
|
||||
addErr = fmt.Errorf("unsupported data file extension %q", pi.Ext())
|
||||
return
|
||||
}
|
||||
|
||||
s := m.s.h.resolveSite(fi.Meta().Lang)
|
||||
f := source.NewFileInfo(fi)
|
||||
h := s.h
|
||||
|
||||
contentAdapter := s.pageMap.treePagesFromTemplateAdapters.Get(pi.Base())
|
||||
var rebuild bool
|
||||
if contentAdapter != nil {
|
||||
// Rebuild
|
||||
contentAdapter = contentAdapter.CloneForGoTmpl(fi)
|
||||
rebuild = true
|
||||
} else {
|
||||
contentAdapter = pagesfromdata.NewPagesFromTemplate(
|
||||
pagesfromdata.PagesFromTemplateOptions{
|
||||
GoTmplFi: fi,
|
||||
Site: s,
|
||||
DepsFromSite: func(s page.Site) pagesfromdata.PagesFromTemplateDeps {
|
||||
ss := s.(*Site)
|
||||
return pagesfromdata.PagesFromTemplateDeps{
|
||||
TmplFinder: ss.TextTmpl(),
|
||||
TmplExec: ss.Tmpl(),
|
||||
}
|
||||
},
|
||||
DependencyManager: s.Conf.NewIdentityManager("pagesfromdata"),
|
||||
Watching: s.Conf.Watching(),
|
||||
HandlePage: func(pt *pagesfromdata.PagesFromTemplate, pc *pagemeta.PageConfig) error {
|
||||
s := pt.Site.(*Site)
|
||||
if err := pc.Compile(pt.GoTmplFi.Meta().PathInfo.Base(), true, "", s.Log, s.conf.MediaTypes.Config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ps, pi, err := h.newPage(
|
||||
&pageMeta{
|
||||
f: f,
|
||||
s: s,
|
||||
pageMetaParams: &pageMetaParams{
|
||||
pageConfig: pc,
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ps == nil {
|
||||
// Disabled page.
|
||||
return nil
|
||||
}
|
||||
|
||||
u, n, replaced := s.pageMap.insertPageWithLock(pi.Base(), ps)
|
||||
|
||||
if h.isRebuild() {
|
||||
if replaced {
|
||||
pt.AddChange(n.GetIdentity())
|
||||
} else {
|
||||
pt.AddChange(u.GetIdentity())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
HandleResource: func(pt *pagesfromdata.PagesFromTemplate, rc *pagemeta.ResourceConfig) error {
|
||||
s := pt.Site.(*Site)
|
||||
if err := rc.Compile(
|
||||
pt.GoTmplFi.Meta().PathInfo.Base(),
|
||||
s.Conf.PathParser(),
|
||||
s.conf.MediaTypes.Config,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rs := &resourceSource{path: rc.PathInfo, rc: rc, opener: nil, fi: pt.GoTmplFi, langIndex: s.languagei}
|
||||
|
||||
_, n, replaced := s.pageMap.insertResourceWithLock(rc.PathInfo.Base(), rs)
|
||||
|
||||
if h.isRebuild() && replaced {
|
||||
pt.AddChange(n.GetIdentity())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
s.pageMap.treePagesFromTemplateAdapters.Insert(pi.Base(), contentAdapter)
|
||||
|
||||
}
|
||||
|
||||
handleBuildInfo := func(s *Site, bi pagesfromdata.BuildInfo) {
|
||||
resourceCount += bi.NumResourcesAdded
|
||||
pageCount += bi.NumPagesAdded
|
||||
s.handleContentAdapterChanges(bi, buildConfig)
|
||||
}
|
||||
|
||||
bi, err := contentAdapter.Execute(context.Background())
|
||||
if err != nil {
|
||||
addErr = err
|
||||
return
|
||||
}
|
||||
handleBuildInfo(s, bi)
|
||||
|
||||
if !rebuild && bi.EnableAllLanguages {
|
||||
// Clone and insert the adapter for the other sites.
|
||||
for _, ss := range s.h.Sites {
|
||||
if s == ss {
|
||||
continue
|
||||
}
|
||||
|
||||
clone := contentAdapter.CloneForSite(ss)
|
||||
|
||||
// Make sure it gets executed for the first time.
|
||||
bi, err := clone.Execute(context.Background())
|
||||
if err != nil {
|
||||
addErr = err
|
||||
return
|
||||
}
|
||||
handleBuildInfo(ss, bi)
|
||||
|
||||
// Insert into the correct language tree so it get rebuilt on changes.
|
||||
ss.pageMap.treePagesFromTemplateAdapters.Insert(pi.Base(), clone)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
// The home page is represented with the zero string.
|
||||
|
||||
+62
-158
@@ -34,9 +34,7 @@ import (
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
"github.com/gohugoio/hugo/hugolib/doctree"
|
||||
"github.com/gohugoio/hugo/hugolib/pagesfromdata"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/gohugoio/hugo/output"
|
||||
"github.com/gohugoio/hugo/resources"
|
||||
"github.com/spf13/cast"
|
||||
@@ -102,8 +100,6 @@ type pageMap struct {
|
||||
cacheContentPlain *dynacache.Partition[string, *resources.StaleValue[contentPlainPlainWords]]
|
||||
contentTableOfContents *dynacache.Partition[string, *resources.StaleValue[contentTableOfContents]]
|
||||
|
||||
contentDataFileSeenItems *maps.Cache[string, map[uint64]bool]
|
||||
|
||||
cfg contentMapConfig
|
||||
}
|
||||
|
||||
@@ -126,10 +122,6 @@ type pageTrees struct {
|
||||
// This tree contains all taxonomy entries, e.g "/tags/blue/page1"
|
||||
treeTaxonomyEntries *doctree.TreeShiftTree[*weightedContentNode]
|
||||
|
||||
// Stores the state for _content.gotmpl files.
|
||||
// Mostly releveant for rebuilds.
|
||||
treePagesFromTemplateAdapters *doctree.TreeShiftTree[*pagesfromdata.PagesFromTemplate]
|
||||
|
||||
// A slice of the resource trees.
|
||||
resourceTrees doctree.MutableTrees
|
||||
}
|
||||
@@ -230,7 +222,6 @@ func (t pageTrees) Shape(d, v int) *pageTrees {
|
||||
t.treePages = t.treePages.Shape(d, v)
|
||||
t.treeResources = t.treeResources.Shape(d, v)
|
||||
t.treeTaxonomyEntries = t.treeTaxonomyEntries.Shape(d, v)
|
||||
t.treePagesFromTemplateAdapters = t.treePagesFromTemplateAdapters.Shape(d, v)
|
||||
t.createMutableTrees()
|
||||
|
||||
return &t
|
||||
@@ -596,9 +587,9 @@ func (m *pageMap) getOrCreateResourcesForPage(ps *pageState) resource.Resources
|
||||
|
||||
sort.SliceStable(res, lessFunc)
|
||||
|
||||
if len(ps.m.pageConfig.ResourcesMeta) > 0 {
|
||||
if len(ps.m.pageConfig.Resources) > 0 {
|
||||
for i, r := range res {
|
||||
res[i] = resources.CloneWithMetadataFromMapIfNeeded(ps.m.pageConfig.ResourcesMeta, r)
|
||||
res[i] = resources.CloneWithMetadataIfNeeded(ps.m.pageConfig.Resources, r)
|
||||
}
|
||||
sort.SliceStable(res, lessFunc)
|
||||
}
|
||||
@@ -676,13 +667,12 @@ type contentNodeShifter struct {
|
||||
numLanguages int
|
||||
}
|
||||
|
||||
func (s *contentNodeShifter) Delete(n contentNodeI, dimension doctree.Dimension) (contentNodeI, bool, bool) {
|
||||
func (s *contentNodeShifter) Delete(n contentNodeI, dimension doctree.Dimension) (bool, bool) {
|
||||
lidx := dimension[0]
|
||||
switch v := n.(type) {
|
||||
case contentNodeIs:
|
||||
deleted := v[lidx]
|
||||
resource.MarkStale(deleted)
|
||||
wasDeleted := deleted != nil
|
||||
resource.MarkStale(v[lidx])
|
||||
wasDeleted := v[lidx] != nil
|
||||
v[lidx] = nil
|
||||
isEmpty := true
|
||||
for _, vv := range v {
|
||||
@@ -691,11 +681,10 @@ func (s *contentNodeShifter) Delete(n contentNodeI, dimension doctree.Dimension)
|
||||
break
|
||||
}
|
||||
}
|
||||
return deleted, wasDeleted, isEmpty
|
||||
return wasDeleted, isEmpty
|
||||
case resourceSources:
|
||||
deleted := v[lidx]
|
||||
resource.MarkStale(deleted)
|
||||
wasDeleted := deleted != nil
|
||||
resource.MarkStale(v[lidx])
|
||||
wasDeleted := v[lidx] != nil
|
||||
v[lidx] = nil
|
||||
isEmpty := true
|
||||
for _, vv := range v {
|
||||
@@ -704,19 +693,19 @@ func (s *contentNodeShifter) Delete(n contentNodeI, dimension doctree.Dimension)
|
||||
break
|
||||
}
|
||||
}
|
||||
return deleted, wasDeleted, isEmpty
|
||||
return wasDeleted, isEmpty
|
||||
case *resourceSource:
|
||||
if lidx != v.LangIndex() {
|
||||
return nil, false, false
|
||||
return false, false
|
||||
}
|
||||
resource.MarkStale(v)
|
||||
return v, true, true
|
||||
return true, true
|
||||
case *pageState:
|
||||
if lidx != v.s.languagei {
|
||||
return nil, false, false
|
||||
return false, false
|
||||
}
|
||||
resource.MarkStale(v)
|
||||
return v, true, true
|
||||
return true, true
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown type %T", n))
|
||||
}
|
||||
@@ -789,7 +778,7 @@ func (s *contentNodeShifter) ForEeachInDimension(n contentNodeI, d int, f func(c
|
||||
}
|
||||
}
|
||||
|
||||
func (s *contentNodeShifter) InsertInto(old, new contentNodeI, dimension doctree.Dimension) (contentNodeI, contentNodeI, bool) {
|
||||
func (s *contentNodeShifter) InsertInto(old, new contentNodeI, dimension doctree.Dimension) contentNodeI {
|
||||
langi := dimension[doctree.DimensionLanguage.Index()]
|
||||
switch vv := old.(type) {
|
||||
case *pageState:
|
||||
@@ -798,39 +787,37 @@ func (s *contentNodeShifter) InsertInto(old, new contentNodeI, dimension doctree
|
||||
panic(fmt.Sprintf("unknown type %T", new))
|
||||
}
|
||||
if vv.s.languagei == newp.s.languagei && newp.s.languagei == langi {
|
||||
return new, vv, true
|
||||
return new
|
||||
}
|
||||
is := make(contentNodeIs, s.numLanguages)
|
||||
is[vv.s.languagei] = old
|
||||
is[langi] = new
|
||||
return is, old, false
|
||||
return is
|
||||
case contentNodeIs:
|
||||
oldv := vv[langi]
|
||||
vv[langi] = new
|
||||
return vv, oldv, oldv != nil
|
||||
return vv
|
||||
case resourceSources:
|
||||
oldv := vv[langi]
|
||||
vv[langi] = new.(*resourceSource)
|
||||
return vv, oldv, oldv != nil
|
||||
return vv
|
||||
case *resourceSource:
|
||||
newp, ok := new.(*resourceSource)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("unknown type %T", new))
|
||||
}
|
||||
if vv.LangIndex() == newp.LangIndex() && newp.LangIndex() == langi {
|
||||
return new, vv, true
|
||||
return new
|
||||
}
|
||||
rs := make(resourceSources, s.numLanguages)
|
||||
rs[vv.LangIndex()] = vv
|
||||
rs[langi] = newp
|
||||
return rs, vv, false
|
||||
return rs
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown type %T", old))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *contentNodeShifter) Insert(old, new contentNodeI) (contentNodeI, contentNodeI, bool) {
|
||||
func (s *contentNodeShifter) Insert(old, new contentNodeI) contentNodeI {
|
||||
switch vv := old.(type) {
|
||||
case *pageState:
|
||||
newp, ok := new.(*pageState)
|
||||
@@ -838,52 +825,40 @@ func (s *contentNodeShifter) Insert(old, new contentNodeI) (contentNodeI, conten
|
||||
panic(fmt.Sprintf("unknown type %T", new))
|
||||
}
|
||||
if vv.s.languagei == newp.s.languagei {
|
||||
if newp != old {
|
||||
resource.MarkStale(old)
|
||||
}
|
||||
return new, vv, true
|
||||
return new
|
||||
}
|
||||
is := make(contentNodeIs, s.numLanguages)
|
||||
is[newp.s.languagei] = new
|
||||
is[vv.s.languagei] = old
|
||||
return is, old, false
|
||||
return is
|
||||
case contentNodeIs:
|
||||
newp, ok := new.(*pageState)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("unknown type %T", new))
|
||||
}
|
||||
oldp := vv[newp.s.languagei]
|
||||
if oldp != newp {
|
||||
resource.MarkStale(oldp)
|
||||
}
|
||||
resource.MarkStale(vv[newp.s.languagei])
|
||||
vv[newp.s.languagei] = new
|
||||
return vv, oldp, oldp != nil
|
||||
return vv
|
||||
case *resourceSource:
|
||||
newp, ok := new.(*resourceSource)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("unknown type %T", new))
|
||||
}
|
||||
if vv.LangIndex() == newp.LangIndex() {
|
||||
if vv != newp {
|
||||
resource.MarkStale(vv)
|
||||
}
|
||||
return new, vv, true
|
||||
return new
|
||||
}
|
||||
rs := make(resourceSources, s.numLanguages)
|
||||
rs[newp.LangIndex()] = newp
|
||||
rs[vv.LangIndex()] = vv
|
||||
return rs, vv, false
|
||||
return rs
|
||||
case resourceSources:
|
||||
newp, ok := new.(*resourceSource)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("unknown type %T", new))
|
||||
}
|
||||
oldp := vv[newp.LangIndex()]
|
||||
if oldp != newp {
|
||||
resource.MarkStale(oldp)
|
||||
}
|
||||
resource.MarkStale(vv[newp.LangIndex()])
|
||||
vv[newp.LangIndex()] = newp
|
||||
return vv, oldp, oldp != nil
|
||||
return vv
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown type %T", old))
|
||||
}
|
||||
@@ -903,8 +878,6 @@ func newPageMap(i int, s *Site, mcache *dynacache.Cache, pageTrees *pageTrees) *
|
||||
cacheContentPlain: dynacache.GetOrCreatePartition[string, *resources.StaleValue[contentPlainPlainWords]](mcache, fmt.Sprintf("/cont/pla/%d", i), dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
|
||||
contentTableOfContents: dynacache.GetOrCreatePartition[string, *resources.StaleValue[contentTableOfContents]](mcache, fmt.Sprintf("/cont/toc/%d", i), dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
|
||||
|
||||
contentDataFileSeenItems: maps.NewCache[string, map[uint64]bool](),
|
||||
|
||||
cfg: contentMapConfig{
|
||||
lang: s.Lang(),
|
||||
taxonomyConfig: taxonomiesConfig.Values(),
|
||||
@@ -932,8 +905,8 @@ func newPageMap(i int, s *Site, mcache *dynacache.Cache, pageTrees *pageTrees) *
|
||||
LockType: doctree.LockTypeRead,
|
||||
Handle: func(s string, n contentNodeI, match doctree.DimensionFlag) (bool, error) {
|
||||
p := n.(*pageState)
|
||||
if p.PathInfo() != nil {
|
||||
add(p.PathInfo().BaseNameNoIdentifier(), p)
|
||||
if p.File() != nil {
|
||||
add(p.File().FileInfo().Meta().PathInfo.BaseNameNoIdentifier(), p)
|
||||
}
|
||||
return false, nil
|
||||
},
|
||||
@@ -975,6 +948,8 @@ type contentTreeReverseIndexMap struct {
|
||||
|
||||
type sitePagesAssembler struct {
|
||||
*Site
|
||||
watching bool
|
||||
incomingChanges *whatChanged
|
||||
assembleChanges *whatChanged
|
||||
ctx context.Context
|
||||
}
|
||||
@@ -1079,7 +1054,7 @@ func (h *HugoSites) resolveAndClearStateForIdentities(
|
||||
)
|
||||
|
||||
for _, id := range changes {
|
||||
if staler, ok := id.(resource.Staler); ok {
|
||||
if staler, ok := id.(resource.Staler); ok && !staler.IsStale() {
|
||||
var msgDetail string
|
||||
if p, ok := id.(*pageState); ok && p.File() != nil {
|
||||
msgDetail = fmt.Sprintf(" (%s)", p.File().Filename())
|
||||
@@ -1093,7 +1068,6 @@ func (h *HugoSites) resolveAndClearStateForIdentities(
|
||||
// 1. Handle the cache busters first, as those may produce identities for the page reset step.
|
||||
// 2. Then reset the page outputs, which may mark some resources as stale.
|
||||
// 3. Then GC the cache.
|
||||
// TOOD1
|
||||
if cachebuster != nil {
|
||||
if err := loggers.TimeTrackfn(func() (logg.LevelLogger, error) {
|
||||
ll := l.WithField("substep", "gc dynacache cachebuster")
|
||||
@@ -1139,33 +1113,6 @@ func (h *HugoSites) resolveAndClearStateForIdentities(
|
||||
}
|
||||
changes = changes[:n]
|
||||
|
||||
if h.pageTrees.treePagesFromTemplateAdapters.LenRaw() > 0 {
|
||||
if err := loggers.TimeTrackfn(func() (logg.LevelLogger, error) {
|
||||
ll := l.WithField("substep", "resolve content adapter change set").WithField("changes", len(changes))
|
||||
checkedCount := 0
|
||||
matchCount := 0
|
||||
depsFinder := identity.NewFinder(identity.FinderConfig{})
|
||||
|
||||
h.pageTrees.treePagesFromTemplateAdapters.WalkPrefixRaw(doctree.LockTypeRead, "",
|
||||
func(s string, n *pagesfromdata.PagesFromTemplate) (bool, error) {
|
||||
for _, id := range changes {
|
||||
checkedCount++
|
||||
if r := depsFinder.Contains(id, n.DependencyManager, 2); r > identity.FinderNotFound {
|
||||
n.MarkStale()
|
||||
matchCount++
|
||||
break
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
|
||||
ll = ll.WithField("checked", checkedCount).WithField("matches", matchCount)
|
||||
return ll, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := loggers.TimeTrackfn(func() (logg.LevelLogger, error) {
|
||||
// changesLeft: The IDs that the pages is dependent on.
|
||||
// changesRight: The IDs that the pages depend on.
|
||||
@@ -1310,7 +1257,6 @@ func (sa *sitePagesAssembler) applyAggregates() error {
|
||||
rw := pw.Extend()
|
||||
rw.Tree = sa.pageMap.treeResources
|
||||
sa.lastmod = time.Time{}
|
||||
rebuild := sa.s.h.isRebuild()
|
||||
|
||||
pw.Handle = func(keyPage string, n contentNodeI, match doctree.DimensionFlag) (bool, error) {
|
||||
pageBundle := n.(*pageState)
|
||||
@@ -1331,7 +1277,7 @@ func (sa *sitePagesAssembler) applyAggregates() error {
|
||||
// Home page gets it's cascade from the site config.
|
||||
cascade = sa.conf.Cascade.Config
|
||||
|
||||
if pageBundle.m.pageConfig.CascadeCompiled == nil {
|
||||
if pageBundle.m.pageConfig.Cascade == nil {
|
||||
// Pass the site cascade downwards.
|
||||
pw.WalkContext.Data().Insert(keyPage, cascade)
|
||||
}
|
||||
@@ -1342,20 +1288,18 @@ func (sa *sitePagesAssembler) applyAggregates() error {
|
||||
}
|
||||
}
|
||||
|
||||
if rebuild {
|
||||
if (pageBundle.IsHome() || pageBundle.IsSection()) && pageBundle.m.setMetaPostCount > 0 {
|
||||
oldDates := pageBundle.m.pageConfig.Dates
|
||||
if (pageBundle.IsHome() || pageBundle.IsSection()) && pageBundle.m.setMetaPostCount > 0 {
|
||||
oldDates := pageBundle.m.pageConfig.Dates
|
||||
|
||||
// We need to wait until after the walk to determine if any of the dates have changed.
|
||||
pw.WalkContext.AddPostHook(
|
||||
func() error {
|
||||
if oldDates != pageBundle.m.pageConfig.Dates {
|
||||
sa.assembleChanges.Add(pageBundle)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
// We need to wait until after the walk to determine if any of the dates have changed.
|
||||
pw.WalkContext.AddPostHook(
|
||||
func() error {
|
||||
if oldDates != pageBundle.m.pageConfig.Dates {
|
||||
sa.assembleChanges.Add(pageBundle)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Combine the cascade map with front matter.
|
||||
@@ -1365,15 +1309,15 @@ func (sa *sitePagesAssembler) applyAggregates() error {
|
||||
|
||||
// We receive cascade values from above. If this leads to a change compared
|
||||
// to the previous value, we need to mark the page and its dependencies as changed.
|
||||
if rebuild && pageBundle.m.setMetaPostCascadeChanged {
|
||||
if pageBundle.m.setMetaPostCascadeChanged {
|
||||
sa.assembleChanges.Add(pageBundle)
|
||||
}
|
||||
|
||||
const eventName = "dates"
|
||||
if n.isContentNodeBranch() {
|
||||
if pageBundle.m.pageConfig.CascadeCompiled != nil {
|
||||
if pageBundle.m.pageConfig.Cascade != nil {
|
||||
// Pass it down.
|
||||
pw.WalkContext.Data().Insert(keyPage, pageBundle.m.pageConfig.CascadeCompiled)
|
||||
pw.WalkContext.Data().Insert(keyPage, pageBundle.m.pageConfig.Cascade)
|
||||
}
|
||||
|
||||
wasZeroDates := pageBundle.m.pageConfig.Dates.IsAllDatesZero()
|
||||
@@ -1474,9 +1418,9 @@ func (sa *sitePagesAssembler) applyAggregatesToTaxonomiesAndTerms() error {
|
||||
p := n.(*pageState)
|
||||
if p.Kind() != kinds.KindTerm {
|
||||
// The other kinds were handled in applyAggregates.
|
||||
if p.m.pageConfig.CascadeCompiled != nil {
|
||||
if p.m.pageConfig.Cascade != nil {
|
||||
// Pass it down.
|
||||
pw.WalkContext.Data().Insert(s, p.m.pageConfig.CascadeCompiled)
|
||||
pw.WalkContext.Data().Insert(s, p.m.pageConfig.Cascade)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1597,7 +1541,7 @@ func (sa *sitePagesAssembler) assembleTermsAndTranslations() error {
|
||||
singular: viewName.singular,
|
||||
s: sa.Site,
|
||||
pathInfo: pi,
|
||||
pageMetaParams: &pageMetaParams{
|
||||
pageMetaParams: pageMetaParams{
|
||||
pageConfig: &pagemeta.PageConfig{
|
||||
Kind: kinds.KindTerm,
|
||||
},
|
||||
@@ -1659,13 +1603,13 @@ func (sa *sitePagesAssembler) assembleResources() error {
|
||||
targetPaths := ps.targetPaths()
|
||||
baseTarget := targetPaths.SubResourceBaseTarget
|
||||
duplicateResourceFiles := true
|
||||
if ps.m.pageConfig.ContentMediaType.IsMarkdown() {
|
||||
if ps.m.pageConfig.IsGoldmark {
|
||||
duplicateResourceFiles = ps.s.ContentSpec.Converters.GetMarkupConfig().Goldmark.DuplicateResourceFiles
|
||||
}
|
||||
|
||||
duplicateResourceFiles = duplicateResourceFiles || ps.s.Conf.IsMultihost()
|
||||
|
||||
err := sa.pageMap.forEachResourceInPage(
|
||||
sa.pageMap.forEachResourceInPage(
|
||||
ps, lockType,
|
||||
!duplicateResourceFiles,
|
||||
func(resourceKey string, n contentNodeI, match doctree.DimensionFlag) (bool, error) {
|
||||
@@ -1691,23 +1635,6 @@ func (sa *sitePagesAssembler) assembleResources() error {
|
||||
|
||||
}
|
||||
|
||||
if rs.rc != nil && rs.rc.Content.IsResourceValue() {
|
||||
if rs.rc.Name == "" {
|
||||
rs.rc.Name = relPathOriginal
|
||||
}
|
||||
r, err := ps.m.s.ResourceSpec.NewResourceWrapperFromResourceConfig(rs.rc)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
rs.r = r
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var mt media.Type
|
||||
if rs.rc != nil {
|
||||
mt = rs.rc.ContentMediaType
|
||||
}
|
||||
|
||||
rd := resources.ResourceSourceDescriptor{
|
||||
OpenReadSeekCloser: rs.opener,
|
||||
Path: rs.path,
|
||||
@@ -1718,23 +1645,8 @@ func (sa *sitePagesAssembler) assembleResources() error {
|
||||
BasePathTargetPath: baseTarget,
|
||||
NameNormalized: relPath,
|
||||
NameOriginal: relPathOriginal,
|
||||
MediaType: mt,
|
||||
LazyPublish: !ps.m.pageConfig.Build.PublishResources,
|
||||
}
|
||||
|
||||
if rs.rc != nil {
|
||||
rc := rs.rc
|
||||
rd.OpenReadSeekCloser = rc.Content.ValueAsOpenReadSeekCloser()
|
||||
if rc.Name != "" {
|
||||
rd.NameNormalized = rc.Name
|
||||
rd.NameOriginal = rc.Name
|
||||
}
|
||||
if rc.Title != "" {
|
||||
rd.Title = rc.Title
|
||||
}
|
||||
rd.Params = rc.Params
|
||||
}
|
||||
|
||||
r, err := ps.m.s.ResourceSpec.NewResource(rd)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -1744,7 +1656,7 @@ func (sa *sitePagesAssembler) assembleResources() error {
|
||||
},
|
||||
)
|
||||
|
||||
return false, err
|
||||
return false, nil
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1851,7 +1763,7 @@ func (sa *sitePagesAssembler) addStandalonePages() error {
|
||||
m := &pageMeta{
|
||||
s: s,
|
||||
pathInfo: s.Conf.PathParser().Parse(files.ComponentFolderContent, key+f.MediaType.FirstSuffix.FullSuffix),
|
||||
pageMetaParams: &pageMetaParams{
|
||||
pageMetaParams: pageMetaParams{
|
||||
pageConfig: &pagemeta.PageConfig{
|
||||
Kind: kind,
|
||||
},
|
||||
@@ -1881,19 +1793,11 @@ func (sa *sitePagesAssembler) addStandalonePages() error {
|
||||
}
|
||||
|
||||
if sitemapEnabled {
|
||||
of := output.SitemapFormat
|
||||
if s.conf.Sitemap.Filename != "" {
|
||||
of.BaseName = paths.Filename(s.conf.Sitemap.Filename)
|
||||
}
|
||||
addStandalone("/_sitemap", kinds.KindSitemap, of)
|
||||
|
||||
addStandalone("/_sitemap", kinds.KindSitemap, output.SitemapFormat)
|
||||
skipSitemapIndex := s.Conf.IsMultihost() || !(s.Conf.DefaultContentLanguageInSubdir() || s.Conf.IsMultilingual())
|
||||
|
||||
if !skipSitemapIndex {
|
||||
of = output.SitemapIndexFormat
|
||||
if s.conf.Sitemap.Filename != "" {
|
||||
of.BaseName = paths.Filename(s.conf.Sitemap.Filename)
|
||||
}
|
||||
addStandalone("/_sitemapindex", kinds.KindSitemapIndex, of)
|
||||
addStandalone("/_sitemapindex", kinds.KindSitemapIndex, output.SitemapIndexFormat)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1977,7 +1881,7 @@ func (sa *sitePagesAssembler) addMissingRootSections() error {
|
||||
m := &pageMeta{
|
||||
s: sa.Site,
|
||||
pathInfo: p,
|
||||
pageMetaParams: &pageMetaParams{
|
||||
pageMetaParams: pageMetaParams{
|
||||
pageConfig: &pagemeta.PageConfig{
|
||||
Kind: kinds.KindHome,
|
||||
},
|
||||
@@ -1987,7 +1891,7 @@ func (sa *sitePagesAssembler) addMissingRootSections() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.Tree.InsertIntoValuesDimensionWithLock(p.Base(), n)
|
||||
w.Tree.InsertWithLock(p.Base(), n)
|
||||
sa.home = n
|
||||
}
|
||||
|
||||
@@ -2010,7 +1914,7 @@ func (sa *sitePagesAssembler) addMissingTaxonomies() error {
|
||||
m := &pageMeta{
|
||||
s: sa.Site,
|
||||
pathInfo: sa.Conf.PathParser().Parse(files.ComponentFolderContent, key+"/_index.md"),
|
||||
pageMetaParams: &pageMetaParams{
|
||||
pageMetaParams: pageMetaParams{
|
||||
pageConfig: &pagemeta.PageConfig{
|
||||
Kind: kinds.KindTaxonomy,
|
||||
},
|
||||
|
||||
+11
-50
@@ -16,7 +16,6 @@ package hugolib
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
@@ -28,7 +27,7 @@ func TestContentMapSite(t *testing.T) {
|
||||
pageTempl := `
|
||||
---
|
||||
title: "Page %d"
|
||||
date: "2019-06-0%d"
|
||||
date: "2019-06-0%d"
|
||||
lastMod: "2019-06-0%d"
|
||||
categories: [%q]
|
||||
---
|
||||
@@ -55,7 +54,7 @@ draft: true
|
||||
title: "Hugo Home"
|
||||
cascade:
|
||||
description: "Common Description"
|
||||
|
||||
|
||||
---
|
||||
|
||||
Home Content.
|
||||
@@ -111,7 +110,7 @@ IsDescendant overlap2: false: {{ $overlap2.IsDescendant $overlap1 }}
|
||||
IsAncestor overlap1: false: {{ $overlap1.IsAncestor $overlap2 }}
|
||||
IsAncestor overlap2: false: {{ $overlap2.IsAncestor $overlap1 }}
|
||||
FirstSection: {{ $blogSub.FirstSection.RelPermalink }} {{ $blog.FirstSection.RelPermalink }} {{ $home.FirstSection.RelPermalink }} {{ $page.FirstSection.RelPermalink }}
|
||||
InSection: true: {{ $page.InSection $blog }} false: {{ $page.InSection $blogSub }}
|
||||
InSection: true: {{ $page.InSection $blog }} false: {{ $page.InSection $blogSub }}
|
||||
Next: {{ $page2.Next.RelPermalink }}
|
||||
NextInSection: {{ $page2.NextInSection.RelPermalink }}
|
||||
Pages: {{ range $blog.Pages }}{{ .RelPermalink }}|{{ end }}
|
||||
@@ -141,11 +140,11 @@ Draft5: {{ if (.Site.GetPage "blog/draftsection/sub/page") }}FOUND{{ end }}|
|
||||
Num Regular: 9
|
||||
Main Sections: [blog]
|
||||
Pag Num Pages: 9
|
||||
|
||||
Home: Hugo Home|/|2019-06-08|Current Section: /|Resources:
|
||||
Blog Section: Blogs|/blog/|2019-06-08|Current Section: /blog|Resources:
|
||||
|
||||
Home: Hugo Home|/|2019-06-08|Current Section: /|Resources:
|
||||
Blog Section: Blogs|/blog/|2019-06-08|Current Section: /blog|Resources:
|
||||
Blog Sub Section: Page 3|/blog/subsection/|2019-06-03|Current Section: /blog/subsection|Resources: application: /blog/subsection/subdata.json|
|
||||
Page: Page 1|/blog/page1/|2019-06-01|Current Section: /blog|Resources:
|
||||
Page: Page 1|/blog/page1/|2019-06-01|Current Section: /blog|Resources:
|
||||
Bundle: Page 12|/blog/bundle/|0001-01-01|Current Section: /blog|Resources: application: /blog/bundle/data.json|page: |
|
||||
IsDescendant: true: true true: true true: true true: true true: true false: false false: false
|
||||
IsAncestor: true: true true: true true: true true: true true: true true: true false: false false: false false: false false: false
|
||||
@@ -154,7 +153,7 @@ Draft5: {{ if (.Site.GetPage "blog/draftsection/sub/page") }}FOUND{{ end }}|
|
||||
IsAncestor overlap1: false: false
|
||||
IsAncestor overlap2: false: false
|
||||
FirstSection: /blog/ /blog/ / /blog/
|
||||
InSection: true: true false: false
|
||||
InSection: true: true false: false
|
||||
Next: /blog/page3/
|
||||
NextInSection: /blog/page3/
|
||||
Pages: /blog/page3/|/blog/subsection/|/blog/page2/|/blog/page1/|/blog/bundle/|
|
||||
@@ -166,13 +165,13 @@ Draft5: {{ if (.Site.GetPage "blog/draftsection/sub/page") }}FOUND{{ end }}|
|
||||
Pag Blog Num Pages: 4
|
||||
Blog Num RegularPages: 4
|
||||
Blog Num Pages: 5
|
||||
|
||||
|
||||
Draft1: |
|
||||
Draft2: FOUND|
|
||||
Draft3: FOUND|
|
||||
Draft4: FOUND|
|
||||
Draft5: FOUND|
|
||||
|
||||
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -186,7 +185,7 @@ title = "Integration Test"
|
||||
disableKinds=["page", "section", "taxonomy", "term", "sitemap", "robotsTXT", "RSS"]
|
||||
-- layouts/index.html --
|
||||
Home: {{ .Title }}|
|
||||
|
||||
|
||||
`
|
||||
|
||||
b := NewIntegrationTestBuilder(
|
||||
@@ -359,41 +358,3 @@ p1-foo.txt
|
||||
b.AssertFileExists("public/s1/p1-foo.txt", true) // failing test
|
||||
b.AssertFileExists("public/s1/p1/index.html", true)
|
||||
}
|
||||
|
||||
func TestSitemapOverrideFilename(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = 'https://example.org/'
|
||||
disableKinds = ['page','rss','section','taxonomy','term']
|
||||
defaultContentLanguage = 'de'
|
||||
defaultContentLanguageInSubdir = true
|
||||
[languages.de]
|
||||
[languages.en]
|
||||
[sitemap]
|
||||
filename = 'foo.xml'
|
||||
-- layouts/index.html --
|
||||
irrelevant
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileExists("public/de/foo.xml", true)
|
||||
b.AssertFileExists("public/en/foo.xml", true)
|
||||
b.AssertFileContent("public/foo.xml",
|
||||
"<loc>https://example.org/de/foo.xml</loc>",
|
||||
"<loc>https://example.org/en/foo.xml</loc>",
|
||||
)
|
||||
|
||||
files = strings.ReplaceAll(files, "filename = 'foo.xml'", "")
|
||||
|
||||
b = Test(t, files)
|
||||
|
||||
b.AssertFileExists("public/de/sitemap.xml", true)
|
||||
b.AssertFileExists("public/en/sitemap.xml", true)
|
||||
b.AssertFileContent("public/sitemap.xml",
|
||||
"<loc>https://example.org/de/sitemap.xml</loc>",
|
||||
"<loc>https://example.org/en/sitemap.xml</loc>",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ func TestTreeInsert(t *testing.T) {
|
||||
c.Assert(tree.Get("/notfound"), qt.IsNil)
|
||||
|
||||
ab2 := &testValue{ID: "/a/b", Lang: 0}
|
||||
v, _, ok := tree.InsertIntoValuesDimension("/a/b", ab2)
|
||||
v, ok := tree.InsertIntoValuesDimension("/a/b", ab2)
|
||||
c.Assert(ok, qt.IsTrue)
|
||||
c.Assert(v, qt.DeepEquals, ab2)
|
||||
|
||||
@@ -239,16 +239,16 @@ func (s *testShifter) ForEeachInDimension(n *testValue, d int, f func(n *testVal
|
||||
f(n)
|
||||
}
|
||||
|
||||
func (s *testShifter) Insert(old, new *testValue) (*testValue, *testValue, bool) {
|
||||
return new, old, true
|
||||
func (s *testShifter) Insert(old, new *testValue) *testValue {
|
||||
return new
|
||||
}
|
||||
|
||||
func (s *testShifter) InsertInto(old, new *testValue, dimension doctree.Dimension) (*testValue, *testValue, bool) {
|
||||
return new, old, true
|
||||
func (s *testShifter) InsertInto(old, new *testValue, dimension doctree.Dimension) *testValue {
|
||||
return new
|
||||
}
|
||||
|
||||
func (s *testShifter) Delete(n *testValue, dimension doctree.Dimension) (*testValue, bool, bool) {
|
||||
return nil, true, true
|
||||
func (s *testShifter) Delete(n *testValue, dimension doctree.Dimension) (bool, bool) {
|
||||
return true, true
|
||||
}
|
||||
|
||||
func (s *testShifter) Shift(n *testValue, dimension doctree.Dimension, exact bool) (*testValue, bool, doctree.DimensionFlag) {
|
||||
|
||||
@@ -38,18 +38,16 @@ type (
|
||||
|
||||
// Insert inserts new into the tree into the dimension it provides.
|
||||
// It may replace old.
|
||||
// It returns the updated and existing T
|
||||
// and a bool indicating if an existing record is updated.
|
||||
Insert(old, new T) (T, T, bool)
|
||||
// It returns a T (can be the same as old).
|
||||
Insert(old, new T) T
|
||||
|
||||
// Insert inserts new into the given dimension.
|
||||
// It may replace old.
|
||||
// It returns the updated and existing T
|
||||
// and a bool indicating if an existing record is updated.
|
||||
InsertInto(old, new T, dimension Dimension) (T, T, bool)
|
||||
// It returns a T (can be the same as old).
|
||||
InsertInto(old, new T, dimension Dimension) T
|
||||
|
||||
// Delete deletes T from the given dimension and returns the deleted T and whether the dimension was deleted and if it's empty after the delete.
|
||||
Delete(v T, dimension Dimension) (T, bool, bool)
|
||||
// Delete deletes T from the given dimension and returns whether the dimension was deleted and if it's empty after the delete.
|
||||
Delete(v T, dimension Dimension) (bool, bool)
|
||||
|
||||
// Shift shifts T into the given dimension
|
||||
// and returns the shifted T and a bool indicating if the shift was successful and
|
||||
@@ -83,11 +81,7 @@ func New[T any](cfg Config[T]) *NodeShiftTree[T] {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *NodeShiftTree[T]) Delete(key string) (T, bool) {
|
||||
return r.delete(key)
|
||||
}
|
||||
|
||||
func (r *NodeShiftTree[T]) DeleteRaw(key string) {
|
||||
func (r *NodeShiftTree[T]) Delete(key string) {
|
||||
r.delete(key)
|
||||
}
|
||||
|
||||
@@ -109,24 +103,23 @@ func (r *NodeShiftTree[T]) DeletePrefix(prefix string) int {
|
||||
return false
|
||||
})
|
||||
for _, key := range keys {
|
||||
if _, ok := r.delete(key); ok {
|
||||
if ok := r.delete(key); ok {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (r *NodeShiftTree[T]) delete(key string) (T, bool) {
|
||||
func (r *NodeShiftTree[T]) delete(key string) bool {
|
||||
var wasDeleted bool
|
||||
var deleted T
|
||||
if v, ok := r.tree.Get(key); ok {
|
||||
var isEmpty bool
|
||||
deleted, wasDeleted, isEmpty = r.shifter.Delete(v.(T), r.dims)
|
||||
wasDeleted, isEmpty = r.shifter.Delete(v.(T), r.dims)
|
||||
if isEmpty {
|
||||
r.tree.Delete(key)
|
||||
}
|
||||
}
|
||||
return deleted, wasDeleted
|
||||
return wasDeleted
|
||||
}
|
||||
|
||||
func (t *NodeShiftTree[T]) DeletePrefixAll(prefix string) int {
|
||||
@@ -148,33 +141,22 @@ func (t *NodeShiftTree[T]) Increment(d int) *NodeShiftTree[T] {
|
||||
return t.Shape(d, t.dims[d]+1)
|
||||
}
|
||||
|
||||
func (r *NodeShiftTree[T]) InsertIntoCurrentDimension(s string, v T) (T, T, bool) {
|
||||
func (r *NodeShiftTree[T]) InsertIntoCurrentDimension(s string, v T) (T, bool) {
|
||||
s = mustValidateKey(cleanKey(s))
|
||||
var (
|
||||
updated bool
|
||||
existing T
|
||||
)
|
||||
if vv, ok := r.tree.Get(s); ok {
|
||||
v, existing, updated = r.shifter.InsertInto(vv.(T), v, r.dims)
|
||||
v = r.shifter.InsertInto(vv.(T), v, r.dims)
|
||||
}
|
||||
r.tree.Insert(s, v)
|
||||
return v, existing, updated
|
||||
return v, true
|
||||
}
|
||||
|
||||
// InsertIntoValuesDimension inserts v into the tree at the given key and the
|
||||
// dimension defined by the value.
|
||||
// It returns the updated and existing T and a bool indicating if an existing record is updated.
|
||||
func (r *NodeShiftTree[T]) InsertIntoValuesDimension(s string, v T) (T, T, bool) {
|
||||
func (r *NodeShiftTree[T]) InsertIntoValuesDimension(s string, v T) (T, bool) {
|
||||
s = mustValidateKey(cleanKey(s))
|
||||
var (
|
||||
updated bool
|
||||
existing T
|
||||
)
|
||||
if vv, ok := r.tree.Get(s); ok {
|
||||
v, existing, updated = r.shifter.Insert(vv.(T), v)
|
||||
v = r.shifter.Insert(vv.(T), v)
|
||||
}
|
||||
r.tree.Insert(s, v)
|
||||
return v, existing, updated
|
||||
return v, true
|
||||
}
|
||||
|
||||
func (r *NodeShiftTree[T]) InsertRawWithLock(s string, v any) (any, bool) {
|
||||
@@ -183,8 +165,7 @@ func (r *NodeShiftTree[T]) InsertRawWithLock(s string, v any) (any, bool) {
|
||||
return r.tree.Insert(s, v)
|
||||
}
|
||||
|
||||
// It returns the updated and existing T and a bool indicating if an existing record is updated.
|
||||
func (r *NodeShiftTree[T]) InsertIntoValuesDimensionWithLock(s string, v T) (T, T, bool) {
|
||||
func (r *NodeShiftTree[T]) InsertWithLock(s string, v T) (T, bool) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
return r.InsertIntoValuesDimension(s, v)
|
||||
|
||||
@@ -28,12 +28,12 @@ type Tree[T any] interface {
|
||||
}
|
||||
|
||||
// NewSimpleTree creates a new SimpleTree.
|
||||
func NewSimpleTree[T comparable]() *SimpleTree[T] {
|
||||
func NewSimpleTree[T any]() *SimpleTree[T] {
|
||||
return &SimpleTree[T]{tree: radix.New()}
|
||||
}
|
||||
|
||||
// SimpleTree is a thread safe radix tree that holds T.
|
||||
type SimpleTree[T comparable] struct {
|
||||
type SimpleTree[T any] struct {
|
||||
mu sync.RWMutex
|
||||
tree *radix.Tree
|
||||
zero T
|
||||
@@ -67,23 +67,16 @@ func (tree *SimpleTree[T]) Insert(s string, v T) T {
|
||||
return v
|
||||
}
|
||||
|
||||
func (tree *SimpleTree[T]) Lock(lockType LockType) func() {
|
||||
func (tree *SimpleTree[T]) WalkPrefix(lockType LockType, s string, f func(s string, v T) (bool, error)) error {
|
||||
switch lockType {
|
||||
case LockTypeNone:
|
||||
return func() {}
|
||||
case LockTypeRead:
|
||||
tree.mu.RLock()
|
||||
return tree.mu.RUnlock
|
||||
defer tree.mu.RUnlock()
|
||||
case LockTypeWrite:
|
||||
tree.mu.Lock()
|
||||
return tree.mu.Unlock
|
||||
defer tree.mu.Unlock()
|
||||
}
|
||||
return func() {}
|
||||
}
|
||||
|
||||
func (tree *SimpleTree[T]) WalkPrefix(lockType LockType, s string, f func(s string, v T) (bool, error)) error {
|
||||
commit := tree.Lock(lockType)
|
||||
defer commit()
|
||||
var err error
|
||||
tree.tree.WalkPrefix(s, func(s string, v any) bool {
|
||||
var b bool
|
||||
|
||||
@@ -113,7 +113,7 @@ type LockType int
|
||||
|
||||
// MutableTree is a tree that can be modified.
|
||||
type MutableTree interface {
|
||||
DeleteRaw(key string)
|
||||
Delete(key string)
|
||||
DeleteAll(key string)
|
||||
DeletePrefix(prefix string) int
|
||||
DeletePrefixAll(prefix string) int
|
||||
@@ -140,9 +140,9 @@ var _ MutableTree = MutableTrees(nil)
|
||||
|
||||
type MutableTrees []MutableTree
|
||||
|
||||
func (t MutableTrees) DeleteRaw(key string) {
|
||||
func (t MutableTrees) Delete(key string) {
|
||||
for _, tree := range t {
|
||||
tree.DeleteRaw(key)
|
||||
tree.Delete(key)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,21 +15,18 @@ package doctree
|
||||
|
||||
var _ Tree[string] = (*TreeShiftTree[string])(nil)
|
||||
|
||||
type TreeShiftTree[T comparable] struct {
|
||||
type TreeShiftTree[T any] struct {
|
||||
// This tree is shiftable in one dimension.
|
||||
d int
|
||||
|
||||
// The value of the current dimension.
|
||||
v int
|
||||
|
||||
// The zero value of T.
|
||||
zero T
|
||||
|
||||
// Will be of length equal to the length of the dimension.
|
||||
trees []*SimpleTree[T]
|
||||
}
|
||||
|
||||
func NewTreeShiftTree[T comparable](d, length int) *TreeShiftTree[T] {
|
||||
func NewTreeShiftTree[T any](d, length int) *TreeShiftTree[T] {
|
||||
if length <= 0 {
|
||||
panic("length must be > 0")
|
||||
}
|
||||
@@ -55,17 +52,6 @@ func (t *TreeShiftTree[T]) Get(s string) T {
|
||||
return t.trees[t.v].Get(s)
|
||||
}
|
||||
|
||||
func (t *TreeShiftTree[T]) DeleteAllFunc(s string, f func(s string, v T) bool) {
|
||||
for _, tt := range t.trees {
|
||||
if v := tt.Get(s); v != t.zero {
|
||||
if f(s, v) {
|
||||
// Delete.
|
||||
tt.tree.Delete(s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TreeShiftTree[T]) LongestPrefix(s string) (string, T) {
|
||||
return t.trees[t.v].LongestPrefix(s)
|
||||
}
|
||||
@@ -74,31 +60,10 @@ func (t *TreeShiftTree[T]) Insert(s string, v T) T {
|
||||
return t.trees[t.v].Insert(s, v)
|
||||
}
|
||||
|
||||
func (t *TreeShiftTree[T]) Lock(lockType LockType) func() {
|
||||
return t.trees[t.v].Lock(lockType)
|
||||
}
|
||||
|
||||
func (t *TreeShiftTree[T]) WalkPrefix(lockType LockType, s string, f func(s string, v T) (bool, error)) error {
|
||||
return t.trees[t.v].WalkPrefix(lockType, s, f)
|
||||
}
|
||||
|
||||
func (t *TreeShiftTree[T]) WalkPrefixRaw(lockType LockType, s string, f func(s string, v T) (bool, error)) error {
|
||||
for _, tt := range t.trees {
|
||||
if err := tt.WalkPrefix(lockType, s, f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TreeShiftTree[T]) LenRaw() int {
|
||||
var count int
|
||||
for _, tt := range t.trees {
|
||||
count += tt.tree.Len()
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (t *TreeShiftTree[T]) Delete(key string) {
|
||||
for _, tt := range t.trees {
|
||||
tt.tree.Delete(key)
|
||||
@@ -112,3 +77,25 @@ func (t *TreeShiftTree[T]) DeletePrefix(prefix string) int {
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (t *TreeShiftTree[T]) Lock(writable bool) (commit func()) {
|
||||
if writable {
|
||||
for _, tt := range t.trees {
|
||||
tt.mu.Lock()
|
||||
}
|
||||
return func() {
|
||||
for _, tt := range t.trees {
|
||||
tt.mu.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, tt := range t.trees {
|
||||
tt.mu.RLock()
|
||||
}
|
||||
return func() {
|
||||
for _, tt := range t.trees {
|
||||
tt.mu.RUnlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,24 +111,6 @@ func (h *HugoSites) ShouldSkipFileChangeEvent(ev fsnotify.Event) bool {
|
||||
return h.skipRebuildForFilenames[ev.Name]
|
||||
}
|
||||
|
||||
func (h *HugoSites) isRebuild() bool {
|
||||
return h.buildCounter.Load() > 0
|
||||
}
|
||||
|
||||
func (h *HugoSites) resolveSite(lang string) *Site {
|
||||
if lang == "" {
|
||||
lang = h.Conf.DefaultContentLanguage()
|
||||
}
|
||||
|
||||
for _, s := range h.Sites {
|
||||
if s.Lang() == lang {
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Only used in tests.
|
||||
type buildCounters struct {
|
||||
contentRenderCounter atomic.Uint64
|
||||
@@ -497,7 +479,6 @@ func (h *HugoSites) loadData() error {
|
||||
hugofs.WalkwayConfig{
|
||||
Fs: h.PathSpec.BaseFs.Data.Fs,
|
||||
IgnoreFile: h.SourceSpec.IgnoreFile,
|
||||
PathParser: h.Conf.PathParser(),
|
||||
WalkFn: func(path string, fi hugofs.FileMetaInfo) error {
|
||||
if fi.IsDir() {
|
||||
return nil
|
||||
|
||||
+82
-124
@@ -23,15 +23,15 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bep/logg"
|
||||
"github.com/gohugoio/hugo/cache/dynacache"
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
"github.com/gohugoio/hugo/hugofs/glob"
|
||||
"github.com/gohugoio/hugo/hugolib/doctree"
|
||||
"github.com/gohugoio/hugo/hugolib/pagesfromdata"
|
||||
"github.com/gohugoio/hugo/hugolib/segments"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/output"
|
||||
@@ -43,11 +43,11 @@ import (
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/para"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/common/rungroup"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
"github.com/gohugoio/hugo/resources/page/siteidentities"
|
||||
"github.com/gohugoio/hugo/resources/postpub"
|
||||
"github.com/gohugoio/hugo/resources/resource"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
|
||||
@@ -99,10 +99,6 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
|
||||
close(to)
|
||||
}(errCollector, errs)
|
||||
|
||||
for _, s := range h.Sites {
|
||||
s.state = siteStateInit
|
||||
}
|
||||
|
||||
if h.Metrics != nil {
|
||||
h.Metrics.Reset()
|
||||
}
|
||||
@@ -116,7 +112,7 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
|
||||
conf := &config
|
||||
if conf.whatChanged == nil {
|
||||
// Assume everything has changed
|
||||
conf.whatChanged = &whatChanged{needsPagesAssembly: true}
|
||||
conf.whatChanged = &whatChanged{contentChanged: true}
|
||||
}
|
||||
|
||||
var prepareErr error
|
||||
@@ -160,10 +156,6 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
|
||||
}
|
||||
}
|
||||
|
||||
for _, s := range h.Sites {
|
||||
s.state = siteStateReady
|
||||
}
|
||||
|
||||
if prepareErr == nil {
|
||||
if err := h.render(infol, conf); err != nil {
|
||||
h.SendError(fmt.Errorf("render: %w", err))
|
||||
@@ -224,7 +216,7 @@ func (h *HugoSites) initRebuild(config *BuildCfg) error {
|
||||
})
|
||||
|
||||
for _, s := range h.Sites {
|
||||
s.resetBuildState(config.whatChanged.needsPagesAssembly)
|
||||
s.resetBuildState(config.whatChanged.contentChanged)
|
||||
}
|
||||
|
||||
h.reset(config)
|
||||
@@ -239,15 +231,11 @@ func (h *HugoSites) process(ctx context.Context, l logg.LevelLogger, config *Bui
|
||||
l = l.WithField("step", "process")
|
||||
defer loggers.TimeTrackf(l, time.Now(), nil, "")
|
||||
|
||||
if _, err := h.init.layouts.Do(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(events) > 0 {
|
||||
// This is a rebuild
|
||||
return h.processPartial(ctx, l, config, init, events)
|
||||
}
|
||||
return h.processFull(ctx, l, config)
|
||||
return h.processFull(ctx, l, *config)
|
||||
}
|
||||
|
||||
// assemble creates missing sections, applies aggregate values (e.g. dates, cascading params),
|
||||
@@ -256,24 +244,22 @@ func (h *HugoSites) assemble(ctx context.Context, l logg.LevelLogger, bcfg *Buil
|
||||
l = l.WithField("step", "assemble")
|
||||
defer loggers.TimeTrackf(l, time.Now(), nil, "")
|
||||
|
||||
if !bcfg.whatChanged.needsPagesAssembly {
|
||||
changes := bcfg.whatChanged.Drain()
|
||||
if len(changes) > 0 {
|
||||
if err := h.resolveAndClearStateForIdentities(ctx, l, nil, changes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !bcfg.whatChanged.contentChanged {
|
||||
return nil
|
||||
}
|
||||
|
||||
h.translationKeyPages.Reset()
|
||||
assemblers := make([]*sitePagesAssembler, len(h.Sites))
|
||||
// Changes detected during assembly (e.g. aggregate date changes)
|
||||
|
||||
assembleChanges := &whatChanged{
|
||||
identitySet: make(map[identity.Identity]bool),
|
||||
}
|
||||
for i, s := range h.Sites {
|
||||
assemblers[i] = &sitePagesAssembler{
|
||||
Site: s,
|
||||
assembleChanges: bcfg.whatChanged,
|
||||
watching: s.watching(),
|
||||
incomingChanges: bcfg.whatChanged,
|
||||
assembleChanges: assembleChanges,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
@@ -289,9 +275,9 @@ func (h *HugoSites) assemble(ctx context.Context, l logg.LevelLogger, bcfg *Buil
|
||||
return err
|
||||
}
|
||||
|
||||
changes := bcfg.whatChanged.Drain()
|
||||
changes := assembleChanges.Changes()
|
||||
|
||||
// Changes from the assemble step (e.g. lastMod, cascade) needs a re-calculation
|
||||
// Changes from the assemble step (e.g. lastMod, cascase) needs a re-calculation
|
||||
// of what needs to be re-built.
|
||||
if len(changes) > 0 {
|
||||
if err := h.resolveAndClearStateForIdentities(ctx, l, nil, changes); err != nil {
|
||||
@@ -328,6 +314,10 @@ func (h *HugoSites) render(l logg.LevelLogger, config *BuildCfg) error {
|
||||
loggers.TimeTrackf(l, start, h.buildCounters.loggFields(), "")
|
||||
}()
|
||||
|
||||
if _, err := h.init.layouts.Do(context.Background()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
siteRenderContext := &siteRenderContext{cfg: config, multihost: h.Configs.IsMultihost}
|
||||
|
||||
i := 0
|
||||
@@ -608,10 +598,6 @@ type pathChange struct {
|
||||
isDir bool
|
||||
}
|
||||
|
||||
func (p pathChange) isStructuralChange() bool {
|
||||
return p.delete || p.isDir
|
||||
}
|
||||
|
||||
// processPartial prepares the Sites' sources for a partial rebuild.
|
||||
func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, config *BuildCfg, init func(config *BuildCfg) error, events []fsnotify.Event) error {
|
||||
h.Log.Trace(logg.StringFunc(func() string {
|
||||
@@ -632,10 +618,10 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
|
||||
logger := h.Log
|
||||
|
||||
var (
|
||||
tmplAdded bool
|
||||
tmplChanged bool
|
||||
i18nChanged bool
|
||||
needsPagesAssemble bool
|
||||
tmplAdded bool
|
||||
tmplChanged bool
|
||||
i18nChanged bool
|
||||
contentChanged bool
|
||||
)
|
||||
|
||||
changedPaths := struct {
|
||||
@@ -709,33 +695,11 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
|
||||
switch pathInfo.Component() {
|
||||
case files.ComponentFolderContent:
|
||||
logger.Println("Source changed", pathInfo.Path())
|
||||
isContentDataFile := pathInfo.IsContentData()
|
||||
if !isContentDataFile {
|
||||
if ids := h.pageTrees.collectAndMarkStaleIdentities(pathInfo); len(ids) > 0 {
|
||||
changes = append(changes, ids...)
|
||||
}
|
||||
} else {
|
||||
h.pageTrees.treePagesFromTemplateAdapters.DeleteAllFunc(pathInfo.Base(),
|
||||
func(s string, n *pagesfromdata.PagesFromTemplate) bool {
|
||||
changes = append(changes, n.DependencyManager)
|
||||
|
||||
// Try to open the file to see if has been deleted.
|
||||
f, err := n.GoTmplFi.Meta().Open()
|
||||
if err == nil {
|
||||
f.Close()
|
||||
}
|
||||
if err != nil {
|
||||
// Remove all pages and resources below.
|
||||
prefix := pathInfo.Base() + "/"
|
||||
h.pageTrees.treePages.DeletePrefixAll(prefix)
|
||||
h.pageTrees.resourceTrees.DeletePrefixAll(prefix)
|
||||
changes = append(changes, identity.NewGlobIdentity(prefix+"*"))
|
||||
}
|
||||
return err != nil
|
||||
})
|
||||
if ids := h.pageTrees.collectAndMarkStaleIdentities(pathInfo); len(ids) > 0 {
|
||||
changes = append(changes, ids...)
|
||||
}
|
||||
|
||||
needsPagesAssemble = true
|
||||
contentChanged = true
|
||||
|
||||
if config.RecentlyVisited != nil {
|
||||
// Fast render mode. Adding them to the visited queue
|
||||
@@ -749,7 +713,7 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
|
||||
|
||||
h.pageTrees.treeTaxonomyEntries.DeletePrefix("")
|
||||
|
||||
if delete && !isContentDataFile {
|
||||
if delete {
|
||||
_, ok := h.pageTrees.treePages.LongestPrefixAll(pathInfo.Base())
|
||||
if ok {
|
||||
h.pageTrees.treePages.DeleteAll(pathInfo.Base())
|
||||
@@ -796,8 +760,48 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
|
||||
}
|
||||
}
|
||||
case files.ComponentFolderAssets:
|
||||
logger.Println("Asset changed", pathInfo.Path())
|
||||
changes = append(changes, pathInfo)
|
||||
p := pathInfo.Path()
|
||||
logger.Println("Asset changed", p)
|
||||
|
||||
var matches []any
|
||||
var mu sync.Mutex
|
||||
|
||||
h.MemCache.ClearMatching(
|
||||
func(k string, pm dynacache.PartitionManager) bool {
|
||||
// Avoid going through everything.
|
||||
return strings.HasPrefix(k, "/res")
|
||||
},
|
||||
func(k, v any) bool {
|
||||
if strings.Contains(k.(string), p) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
switch vv := v.(type) {
|
||||
case resource.Resources:
|
||||
// GetMatch/Match.
|
||||
for _, r := range vv {
|
||||
matches = append(matches, r)
|
||||
}
|
||||
return true
|
||||
default:
|
||||
matches = append(matches, vv)
|
||||
return true
|
||||
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
var hasID bool
|
||||
for _, r := range matches {
|
||||
identity.WalkIdentitiesShallow(r, func(level int, rid identity.Identity) bool {
|
||||
hasID = true
|
||||
changes = append(changes, rid)
|
||||
return false
|
||||
})
|
||||
}
|
||||
if !hasID {
|
||||
changes = append(changes, pathInfo)
|
||||
}
|
||||
case files.ComponentFolderData:
|
||||
logger.Println("Data changed", pathInfo.Path())
|
||||
|
||||
@@ -888,8 +892,8 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
|
||||
resourceFiles := h.fileEventsContentPaths(addedOrChangedContent)
|
||||
|
||||
changed := &whatChanged{
|
||||
needsPagesAssembly: needsPagesAssemble,
|
||||
identitySet: make(identity.Identities),
|
||||
contentChanged: contentChanged,
|
||||
identitySet: make(identity.Identities),
|
||||
}
|
||||
changed.Add(changes...)
|
||||
|
||||
@@ -911,13 +915,17 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.resolveAndClearStateForIdentities(ctx, l, cacheBusterOr, changed.Drain()); err != nil {
|
||||
// Removes duplicates.
|
||||
changes = changed.identitySet.AsSlice()
|
||||
|
||||
if err := h.resolveAndClearStateForIdentities(ctx, l, cacheBusterOr, changes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tmplChanged || i18nChanged {
|
||||
// TODO(bep) we should split this, but currently the loading of i18n and layout files are tied together. See #12048.
|
||||
h.init.layouts.Reset()
|
||||
|
||||
if err := loggers.TimeTrackfn(func() (logg.LevelLogger, error) {
|
||||
// TODO(bep) this could probably be optimized to somehow
|
||||
// only load the changed templates and its dependencies, but that is non-trivial.
|
||||
@@ -938,21 +946,17 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
|
||||
}
|
||||
|
||||
if resourceFiles != nil {
|
||||
if err := h.processFiles(ctx, l, config, resourceFiles...); err != nil {
|
||||
if err := h.processFiles(ctx, l, *config, resourceFiles...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if h.isRebuild() {
|
||||
if err := h.processContentAdaptersOnRebuild(ctx, config); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
h.logServerAddresses()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HugoSites) LogServerAddresses() {
|
||||
func (h *HugoSites) logServerAddresses() {
|
||||
if h.hugoInfo.IsMultihost() {
|
||||
for _, s := range h.Sites {
|
||||
h.Log.Printf("Web Server is available at %s (bind address %s) %s\n", s.conf.C.BaseURL, s.conf.C.ServerInterface, s.Language().Lang)
|
||||
@@ -963,7 +967,7 @@ func (h *HugoSites) LogServerAddresses() {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HugoSites) processFull(ctx context.Context, l logg.LevelLogger, config *BuildCfg) (err error) {
|
||||
func (h *HugoSites) processFull(ctx context.Context, l logg.LevelLogger, config BuildCfg) (err error) {
|
||||
if err = h.processFiles(ctx, l, config); err != nil {
|
||||
err = fmt.Errorf("readAndProcessContent: %w", err)
|
||||
return
|
||||
@@ -971,53 +975,7 @@ func (h *HugoSites) processFull(ctx context.Context, l logg.LevelLogger, config
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Site) handleContentAdapterChanges(bi pagesfromdata.BuildInfo, buildConfig *BuildCfg) {
|
||||
if !s.h.isRebuild() {
|
||||
return
|
||||
}
|
||||
|
||||
if len(bi.ChangedIdentities) > 0 {
|
||||
buildConfig.whatChanged.Add(bi.ChangedIdentities...)
|
||||
buildConfig.whatChanged.needsPagesAssembly = true
|
||||
}
|
||||
|
||||
for _, p := range bi.DeletedPaths {
|
||||
pp := path.Join(bi.Path.Base(), p)
|
||||
if v, ok := s.pageMap.treePages.Delete(pp); ok {
|
||||
buildConfig.whatChanged.Add(v.GetIdentity())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HugoSites) processContentAdaptersOnRebuild(ctx context.Context, buildConfig *BuildCfg) error {
|
||||
// Make sure the layouts are initialized.
|
||||
if _, err := h.init.layouts.Do(context.Background()); err != nil {
|
||||
return err
|
||||
}
|
||||
g := rungroup.Run[*pagesfromdata.PagesFromTemplate](ctx, rungroup.Config[*pagesfromdata.PagesFromTemplate]{
|
||||
NumWorkers: h.numWorkers,
|
||||
Handle: func(ctx context.Context, p *pagesfromdata.PagesFromTemplate) error {
|
||||
bi, err := p.Execute(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s := p.Site.(*Site)
|
||||
s.handleContentAdapterChanges(bi, buildConfig)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
h.pageTrees.treePagesFromTemplateAdapters.WalkPrefixRaw(doctree.LockTypeRead, "", func(key string, p *pagesfromdata.PagesFromTemplate) (bool, error) {
|
||||
if p.StaleVersion() > 0 {
|
||||
g.Enqueue(p)
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
func (s *HugoSites) processFiles(ctx context.Context, l logg.LevelLogger, buildConfig *BuildCfg, filenames ...pathChange) error {
|
||||
func (s *HugoSites) processFiles(ctx context.Context, l logg.LevelLogger, buildConfig BuildCfg, filenames ...pathChange) error {
|
||||
if s.Deps == nil {
|
||||
panic("nil deps on site")
|
||||
}
|
||||
@@ -1027,7 +985,7 @@ func (s *HugoSites) processFiles(ctx context.Context, l logg.LevelLogger, buildC
|
||||
// For inserts, we can pick an arbitrary pageMap.
|
||||
pageMap := s.Sites[0].pageMap
|
||||
|
||||
c := newPagesCollector(ctx, s.h, sourceSpec, s.Log, l, pageMap, buildConfig, filenames)
|
||||
c := newPagesCollector(ctx, s.h, sourceSpec, s.Log, l, pageMap, filenames)
|
||||
|
||||
if err := c.Collect(); err != nil {
|
||||
return err
|
||||
|
||||
@@ -252,31 +252,3 @@ Files: {{ range $files }}{{ .Permalink }}|{{ end }}$
|
||||
b.AssertFileContent("public/en/enpages/mybundle-en/file2.txt", "File 2 en.")
|
||||
b.AssertFileContent("public/fr/section/mybundle/file2.txt", "File 2 en.")
|
||||
}
|
||||
|
||||
func TestMultihostAllButOneLanguageDisabledIssue12288(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
defaultContentLanguage = "en"
|
||||
disableLanguages = ["fr"]
|
||||
#baseURL = "https://example.com"
|
||||
[languages]
|
||||
[languages.en]
|
||||
baseURL = "https://example.en"
|
||||
weight = 1
|
||||
[languages.fr]
|
||||
baseURL = "https://example.fr"
|
||||
weight = 2
|
||||
-- assets/css/main.css --
|
||||
body { color: red; }
|
||||
-- layouts/index.html --
|
||||
{{ $css := resources.Get "css/main.css" | minify }}
|
||||
CSS: {{ $css.Permalink }}|{{ $css.RelPermalink }}|
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/css/main.min.css", "body{color:red}")
|
||||
b.AssertFileContent("public/index.html", "CSS: https://example.en/css/main.min.css|/css/main.min.css|")
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ Home: {{ .Title }}
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
// LogLevel: logg.LevelTrace,
|
||||
LogLevel: logg.LevelTrace,
|
||||
},
|
||||
).Build()
|
||||
|
||||
|
||||
@@ -38,20 +38,12 @@ import (
|
||||
|
||||
type TestOpt func(*IntegrationTestConfig)
|
||||
|
||||
// TestOptRunning will enable running in integration tests.
|
||||
func TestOptRunning() TestOpt {
|
||||
return func(c *IntegrationTestConfig) {
|
||||
c.Running = true
|
||||
}
|
||||
}
|
||||
|
||||
// TestOptWatching will enable watching in integration tests.
|
||||
func TestOptWatching() TestOpt {
|
||||
return func(c *IntegrationTestConfig) {
|
||||
c.Watching = true
|
||||
}
|
||||
}
|
||||
|
||||
// Enable tracing in integration tests.
|
||||
// THis should only be used during development and not committed to the repo.
|
||||
func TestOptTrace() TestOpt {
|
||||
@@ -578,10 +570,6 @@ func (s *IntegrationTestBuilder) initBuilder() error {
|
||||
"running": s.Cfg.Running,
|
||||
"watch": s.Cfg.Running,
|
||||
})
|
||||
} else if s.Cfg.Watching {
|
||||
flags.Set("internal", maps.Params{
|
||||
"watch": s.Cfg.Watching,
|
||||
})
|
||||
}
|
||||
|
||||
if s.Cfg.WorkingDir != "" {
|
||||
@@ -829,11 +817,6 @@ type IntegrationTestConfig struct {
|
||||
// Whether to simulate server mode.
|
||||
Running bool
|
||||
|
||||
// Watch for changes.
|
||||
// This is (currently) always set to true when Running is set.
|
||||
// Note that the CLI for the server does allow for --watch=false, but that is not used in these test.
|
||||
Watching bool
|
||||
|
||||
// Will print the log buffer after the build
|
||||
Verbose bool
|
||||
|
||||
|
||||
+2
-12
@@ -510,15 +510,9 @@ func (p *pageState) renderResources() error {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, isWrapper := r.(resource.ResourceWrapper); isWrapper {
|
||||
// Skip resources that are wrapped.
|
||||
// These gets published on its own.
|
||||
continue
|
||||
}
|
||||
|
||||
src, ok := r.(resource.Source)
|
||||
if !ok {
|
||||
initErr = fmt.Errorf("resource %T does not support resource.Source", r)
|
||||
initErr = fmt.Errorf("resource %T does not support resource.Source", src)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -587,11 +581,7 @@ func (p *pageState) getPageInfoForError() string {
|
||||
func (p *pageState) getContentConverter() converter.Converter {
|
||||
var err error
|
||||
p.contentConverterInit.Do(func() {
|
||||
if p.m.pageConfig.ContentMediaType.IsZero() {
|
||||
panic("ContentMediaType not set")
|
||||
}
|
||||
markup := p.m.pageConfig.ContentMediaType.SubType
|
||||
|
||||
markup := p.m.pageConfig.Markup
|
||||
if markup == "html" {
|
||||
// Only used for shortcode inner content.
|
||||
markup = "markdown"
|
||||
|
||||
@@ -67,7 +67,6 @@ type pageCommon struct {
|
||||
page.InSectionPositioner
|
||||
page.OutputFormatsProvider
|
||||
page.PageMetaProvider
|
||||
page.PageMetaInternalProvider
|
||||
page.Positioner
|
||||
page.RawContentProvider
|
||||
page.RelatedKeywordsProvider
|
||||
|
||||
+27
-51
@@ -20,7 +20,6 @@ import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
@@ -55,31 +54,21 @@ type pageContentReplacement struct {
|
||||
source pageparser.Item
|
||||
}
|
||||
|
||||
func (m *pageMeta) parseFrontMatter(h *HugoSites, pid uint64) (*contentParseInfo, error) {
|
||||
var (
|
||||
sourceKey string
|
||||
openSource hugio.OpenReadSeekCloser
|
||||
isFromContentAdapter = m.pageConfig.IsFromContentAdapter
|
||||
)
|
||||
|
||||
if m.f != nil && !isFromContentAdapter {
|
||||
sourceKey = filepath.ToSlash(m.f.Filename())
|
||||
if !isFromContentAdapter {
|
||||
meta := m.f.FileInfo().Meta()
|
||||
openSource = func() (hugio.ReadSeekCloser, error) {
|
||||
r, err := meta.Open()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open file %q: %w", meta.Filename, err)
|
||||
}
|
||||
return r, nil
|
||||
func (m *pageMeta) parseFrontMatter(h *HugoSites, pid uint64, sourceKey string) (*contentParseInfo, error) {
|
||||
var openSource hugio.OpenReadSeekCloser
|
||||
if m.f != nil {
|
||||
meta := m.f.FileInfo().Meta()
|
||||
openSource = func() (hugio.ReadSeekCloser, error) {
|
||||
r, err := meta.Open()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open file %q: %w", meta.Filename, err)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
} else if isFromContentAdapter {
|
||||
openSource = m.pageConfig.Content.ValueAsOpenReadSeekCloser()
|
||||
}
|
||||
|
||||
if sourceKey == "" {
|
||||
sourceKey = strconv.FormatUint(pid, 10)
|
||||
sourceKey = strconv.Itoa(int(pid))
|
||||
}
|
||||
|
||||
pi := &contentParseInfo{
|
||||
@@ -96,9 +85,7 @@ func (m *pageMeta) parseFrontMatter(h *HugoSites, pid uint64) (*contentParseInfo
|
||||
|
||||
items, err := pageparser.ParseBytes(
|
||||
source,
|
||||
pageparser.Config{
|
||||
NoFrontMatter: isFromContentAdapter,
|
||||
},
|
||||
pageparser.Config{},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -106,11 +93,6 @@ func (m *pageMeta) parseFrontMatter(h *HugoSites, pid uint64) (*contentParseInfo
|
||||
|
||||
pi.itemsStep1 = items
|
||||
|
||||
if isFromContentAdapter {
|
||||
// No front matter.
|
||||
return pi, nil
|
||||
}
|
||||
|
||||
if err := pi.mapFrontMatter(source); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -436,8 +418,6 @@ func (c *cachedContent) mustSource() []byte {
|
||||
|
||||
func (c *contentParseInfo) contentSource(s resource.StaleInfo) ([]byte, error) {
|
||||
key := c.sourceKey
|
||||
versionv := s.StaleVersion()
|
||||
|
||||
v, err := c.h.cacheContentSource.GetOrCreate(key, func(string) (*resources.StaleValue[[]byte], error) {
|
||||
b, err := c.readSourceAll()
|
||||
if err != nil {
|
||||
@@ -446,8 +426,8 @@ func (c *contentParseInfo) contentSource(s resource.StaleInfo) ([]byte, error) {
|
||||
|
||||
return &resources.StaleValue[[]byte]{
|
||||
Value: b,
|
||||
StaleVersionFunc: func() uint32 {
|
||||
return s.StaleVersion() - versionv
|
||||
IsStaleFunc: func() bool {
|
||||
return s.IsStale()
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
@@ -507,7 +487,7 @@ type contentPlainPlainWords struct {
|
||||
func (c *cachedContent) contentRendered(ctx context.Context, cp *pageContentOutput) (contentSummary, error) {
|
||||
ctx = tpl.Context.DependencyScope.Set(ctx, pageDependencyScopeGlobal)
|
||||
key := c.pi.sourceKey + "/" + cp.po.f.Name
|
||||
versionv := c.version(cp)
|
||||
versionv := cp.contentRenderedVersion
|
||||
|
||||
v, err := c.pm.cacheContentRendered.GetOrCreate(key, func(string) (*resources.StaleValue[contentSummary], error) {
|
||||
cp.po.p.s.Log.Trace(logg.StringFunc(func() string {
|
||||
@@ -524,8 +504,8 @@ func (c *cachedContent) contentRendered(ctx context.Context, cp *pageContentOutp
|
||||
}
|
||||
|
||||
rs := &resources.StaleValue[contentSummary]{
|
||||
StaleVersionFunc: func() uint32 {
|
||||
return c.version(cp) - versionv
|
||||
IsStaleFunc: func() bool {
|
||||
return c.IsStale() || cp.contentRenderedVersion != versionv
|
||||
},
|
||||
}
|
||||
|
||||
@@ -585,14 +565,15 @@ func (c *cachedContent) contentRendered(ctx context.Context, cp *pageContentOutp
|
||||
var result contentSummary // hasVariants bool
|
||||
|
||||
if c.pi.hasSummaryDivider {
|
||||
if cp.po.p.m.pageConfig.ContentMediaType.IsHTML() {
|
||||
isHTML := cp.po.p.m.pageConfig.Markup == "html"
|
||||
if isHTML {
|
||||
// Use the summary sections as provided by the user.
|
||||
i := bytes.Index(b, internalSummaryDividerPre)
|
||||
result.summary = helpers.BytesToHTML(b[:i])
|
||||
b = b[i+len(internalSummaryDividerPre):]
|
||||
|
||||
} else {
|
||||
summary, content, err := splitUserDefinedSummaryAndContent(cp.po.p.m.pageConfig.Content.Markup, b)
|
||||
summary, content, err := splitUserDefinedSummaryAndContent(cp.po.p.m.pageConfig.Markup, b)
|
||||
if err != nil {
|
||||
cp.po.p.s.Log.Errorf("Failed to set user defined summary for page %q: %s", cp.po.p.pathOrTitle(), err)
|
||||
} else {
|
||||
@@ -626,7 +607,7 @@ var setGetContentCallbackInContext = hcontext.NewContextDispatcher[func(*pageCon
|
||||
|
||||
func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (contentTableOfContents, error) {
|
||||
key := c.pi.sourceKey + "/" + cp.po.f.Name
|
||||
versionv := c.version(cp)
|
||||
versionv := cp.contentRenderedVersion
|
||||
|
||||
v, err := c.pm.contentTableOfContents.GetOrCreate(key, func(string) (*resources.StaleValue[contentTableOfContents], error) {
|
||||
source, err := c.pi.contentSource(c)
|
||||
@@ -682,7 +663,7 @@ func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (
|
||||
p.pageOutputTemplateVariationsState.Add(1)
|
||||
}
|
||||
|
||||
isHTML := cp.po.p.m.pageConfig.ContentMediaType.IsHTML()
|
||||
isHTML := cp.po.p.m.pageConfig.Markup == "html"
|
||||
|
||||
if !isHTML {
|
||||
createAndSetToC := func(tocProvider converter.TableOfContentsProvider) {
|
||||
@@ -732,8 +713,8 @@ func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (
|
||||
|
||||
return &resources.StaleValue[contentTableOfContents]{
|
||||
Value: ct,
|
||||
StaleVersionFunc: func() uint32 {
|
||||
return c.version(cp) - versionv
|
||||
IsStaleFunc: func() bool {
|
||||
return c.IsStale() || cp.contentRenderedVersion != versionv
|
||||
},
|
||||
}, nil
|
||||
})
|
||||
@@ -744,21 +725,16 @@ func (c *cachedContent) contentToC(ctx context.Context, cp *pageContentOutput) (
|
||||
return v.Value, nil
|
||||
}
|
||||
|
||||
func (c *cachedContent) version(cp *pageContentOutput) uint32 {
|
||||
// Both of these gets incremented on change.
|
||||
return c.StaleVersion() + cp.contentRenderedVersion
|
||||
}
|
||||
|
||||
func (c *cachedContent) contentPlain(ctx context.Context, cp *pageContentOutput) (contentPlainPlainWords, error) {
|
||||
key := c.pi.sourceKey + "/" + cp.po.f.Name
|
||||
|
||||
versionv := c.version(cp)
|
||||
versionv := cp.contentRenderedVersion
|
||||
|
||||
v, err := c.pm.cacheContentPlain.GetOrCreateWitTimeout(key, cp.po.p.s.Conf.Timeout(), func(string) (*resources.StaleValue[contentPlainPlainWords], error) {
|
||||
var result contentPlainPlainWords
|
||||
rs := &resources.StaleValue[contentPlainPlainWords]{
|
||||
StaleVersionFunc: func() uint32 {
|
||||
return c.version(cp) - versionv
|
||||
IsStaleFunc: func() bool {
|
||||
return c.IsStale() || cp.contentRenderedVersion != versionv
|
||||
},
|
||||
}
|
||||
|
||||
@@ -805,7 +781,7 @@ func (c *cachedContent) contentPlain(ctx context.Context, cp *pageContentOutput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
html := cp.po.p.s.ContentSpec.TrimShortHTML(b.Bytes(), cp.po.p.m.pageConfig.Content.Markup)
|
||||
html := cp.po.p.s.ContentSpec.TrimShortHTML(b.Bytes(), cp.po.p.m.pageConfig.Markup)
|
||||
result.summary = helpers.BytesToHTML(html)
|
||||
} else {
|
||||
var summary string
|
||||
|
||||
+1
-15
@@ -62,22 +62,8 @@ func (p *pageMenus) init() {
|
||||
p.p,
|
||||
)
|
||||
|
||||
params := p.p.Params()
|
||||
|
||||
var menus any
|
||||
var ok bool
|
||||
|
||||
if p.p.m.pageConfig.Menus != nil {
|
||||
menus = p.p.m.pageConfig.Menus
|
||||
} else {
|
||||
menus, ok = params["menus"]
|
||||
if !ok {
|
||||
menus = params["menu"]
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
p.pm, err = navigation.PageMenusFromPage(menus, p.p)
|
||||
p.pm, err = navigation.PageMenusFromPage(p.p)
|
||||
if err != nil {
|
||||
p.p.s.Log.Errorln(p.p.wrapError(err))
|
||||
}
|
||||
|
||||
+32
-50
@@ -54,7 +54,7 @@ type pageMeta struct {
|
||||
singular string // Set for kind == KindTerm and kind == KindTaxonomy.
|
||||
|
||||
resource.Staler
|
||||
*pageMetaParams
|
||||
pageMetaParams
|
||||
pageMetaFrontMatter
|
||||
|
||||
// Set for standalone pages, e.g. robotsTXT.
|
||||
@@ -74,9 +74,7 @@ type pageMeta struct {
|
||||
// Prepare for a rebuild of the data passed in from front matter.
|
||||
func (m *pageMeta) setMetaPostPrepareRebuild() {
|
||||
params := xmaps.Clone[map[string]any](m.paramsOriginal)
|
||||
m.pageMetaParams.pageConfig = &pagemeta.PageConfig{
|
||||
Params: params,
|
||||
}
|
||||
m.pageMetaParams.pageConfig.Params = params
|
||||
m.pageMetaFrontMatter = pageMetaFrontMatter{}
|
||||
}
|
||||
|
||||
@@ -100,7 +98,7 @@ type pageMetaFrontMatter struct {
|
||||
func (m *pageMetaParams) init(preserveOringal bool) {
|
||||
if preserveOringal {
|
||||
m.paramsOriginal = xmaps.Clone[maps.Params](m.pageConfig.Params)
|
||||
m.cascadeOriginal = xmaps.Clone[map[page.PageMatcher]maps.Params](m.pageConfig.CascadeCompiled)
|
||||
m.cascadeOriginal = xmaps.Clone[map[page.PageMatcher]maps.Params](m.pageConfig.Cascade)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,19 +135,19 @@ func (p *pageMeta) BundleType() string {
|
||||
}
|
||||
|
||||
func (p *pageMeta) Date() time.Time {
|
||||
return p.pageConfig.Dates.Date
|
||||
return p.pageConfig.Date
|
||||
}
|
||||
|
||||
func (p *pageMeta) PublishDate() time.Time {
|
||||
return p.pageConfig.Dates.PublishDate
|
||||
return p.pageConfig.PublishDate
|
||||
}
|
||||
|
||||
func (p *pageMeta) Lastmod() time.Time {
|
||||
return p.pageConfig.Dates.Lastmod
|
||||
return p.pageConfig.Lastmod
|
||||
}
|
||||
|
||||
func (p *pageMeta) ExpiryDate() time.Time {
|
||||
return p.pageConfig.Dates.ExpiryDate
|
||||
return p.pageConfig.ExpiryDate
|
||||
}
|
||||
|
||||
func (p *pageMeta) Description() string {
|
||||
@@ -277,9 +275,11 @@ func (p *pageMeta) Weight() int {
|
||||
|
||||
func (p *pageMeta) setMetaPre(pi *contentParseInfo, logger loggers.Logger, conf config.AllProvider) error {
|
||||
frontmatter := pi.frontMatter
|
||||
|
||||
if frontmatter != nil {
|
||||
pcfg := p.pageConfig
|
||||
if pcfg == nil {
|
||||
panic("pageConfig not set")
|
||||
}
|
||||
// Needed for case insensitive fetching of params values
|
||||
maps.PrepareParams(frontmatter)
|
||||
pcfg.Params = frontmatter
|
||||
@@ -290,7 +290,7 @@ func (p *pageMeta) setMetaPre(pi *contentParseInfo, logger loggers.Logger, conf
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pcfg.CascadeCompiled = cascade
|
||||
pcfg.Cascade = cascade
|
||||
}
|
||||
|
||||
// Look for path, lang and kind, all of which values we need early on.
|
||||
@@ -328,18 +328,18 @@ func (ps *pageState) setMetaPost(cascade map[page.PageMatcher]maps.Params) error
|
||||
ps.m.setMetaPostCount++
|
||||
var cascadeHashPre uint64
|
||||
if ps.m.setMetaPostCount > 1 {
|
||||
cascadeHashPre = identity.HashUint64(ps.m.pageConfig.CascadeCompiled)
|
||||
ps.m.pageConfig.CascadeCompiled = xmaps.Clone[map[page.PageMatcher]maps.Params](ps.m.cascadeOriginal)
|
||||
cascadeHashPre = identity.HashUint64(ps.m.pageConfig.Cascade)
|
||||
ps.m.pageConfig.Cascade = xmaps.Clone[map[page.PageMatcher]maps.Params](ps.m.cascadeOriginal)
|
||||
|
||||
}
|
||||
|
||||
// Apply cascades first so they can be overridden later.
|
||||
if cascade != nil {
|
||||
if ps.m.pageConfig.CascadeCompiled != nil {
|
||||
if ps.m.pageConfig.Cascade != nil {
|
||||
for k, v := range cascade {
|
||||
vv, found := ps.m.pageConfig.CascadeCompiled[k]
|
||||
vv, found := ps.m.pageConfig.Cascade[k]
|
||||
if !found {
|
||||
ps.m.pageConfig.CascadeCompiled[k] = v
|
||||
ps.m.pageConfig.Cascade[k] = v
|
||||
} else {
|
||||
// Merge
|
||||
for ck, cv := range v {
|
||||
@@ -349,20 +349,19 @@ func (ps *pageState) setMetaPost(cascade map[page.PageMatcher]maps.Params) error
|
||||
}
|
||||
}
|
||||
}
|
||||
cascade = ps.m.pageConfig.CascadeCompiled
|
||||
cascade = ps.m.pageConfig.Cascade
|
||||
} else {
|
||||
ps.m.pageConfig.CascadeCompiled = cascade
|
||||
ps.m.pageConfig.Cascade = cascade
|
||||
}
|
||||
}
|
||||
|
||||
if cascade == nil {
|
||||
cascade = ps.m.pageConfig.CascadeCompiled
|
||||
cascade = ps.m.pageConfig.Cascade
|
||||
}
|
||||
|
||||
if ps.m.setMetaPostCount > 1 {
|
||||
ps.m.setMetaPostCascadeChanged = cascadeHashPre != identity.HashUint64(ps.m.pageConfig.CascadeCompiled)
|
||||
ps.m.setMetaPostCascadeChanged = cascadeHashPre != identity.HashUint64(ps.m.pageConfig.Cascade)
|
||||
if !ps.m.setMetaPostCascadeChanged {
|
||||
|
||||
// No changes, restore any value that may be changed by aggregation.
|
||||
ps.m.pageConfig.Dates = ps.m.datesOriginal
|
||||
return nil
|
||||
@@ -401,17 +400,11 @@ func (p *pageState) setMetaPostParams() error {
|
||||
pm := p.m
|
||||
var mtime time.Time
|
||||
var contentBaseName string
|
||||
var ext string
|
||||
var isContentAdapter bool
|
||||
if p.File() != nil {
|
||||
isContentAdapter = p.File().IsContentAdapter()
|
||||
contentBaseName = p.File().ContentBaseName()
|
||||
if p.File().FileInfo() != nil {
|
||||
mtime = p.File().FileInfo().ModTime()
|
||||
}
|
||||
if !isContentAdapter {
|
||||
ext = p.File().Ext()
|
||||
}
|
||||
}
|
||||
|
||||
var gitAuthorDate time.Time
|
||||
@@ -435,11 +428,6 @@ func (p *pageState) setMetaPostParams() error {
|
||||
p.s.Log.Errorf("Failed to handle dates for page %q: %s", p.pathOrTitle(), err)
|
||||
}
|
||||
|
||||
if isContentAdapter {
|
||||
// Done.
|
||||
return nil
|
||||
}
|
||||
|
||||
var buildConfig any
|
||||
var isNewBuildKeyword bool
|
||||
if v, ok := pm.pageConfig.Params["_build"]; ok {
|
||||
@@ -468,10 +456,8 @@ params:
|
||||
var sitemapSet bool
|
||||
|
||||
pcfg := pm.pageConfig
|
||||
|
||||
params := pcfg.Params
|
||||
if params == nil {
|
||||
panic("params not set for " + p.Title())
|
||||
}
|
||||
|
||||
var draft, published, isCJKLanguage *bool
|
||||
var userParams map[string]any
|
||||
@@ -564,8 +550,8 @@ params:
|
||||
pcfg.Layout = cast.ToString(v)
|
||||
params[loki] = pcfg.Layout
|
||||
case "markup":
|
||||
pcfg.Content.Markup = cast.ToString(v)
|
||||
params[loki] = pcfg.Content.Markup
|
||||
pcfg.Markup = cast.ToString(v)
|
||||
params[loki] = pcfg.Markup
|
||||
case "weight":
|
||||
pcfg.Weight = cast.ToInt(v)
|
||||
params[loki] = pcfg.Weight
|
||||
@@ -615,7 +601,7 @@ params:
|
||||
}
|
||||
|
||||
if handled {
|
||||
pcfg.ResourcesMeta = resources
|
||||
pcfg.Resources = resources
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
@@ -662,6 +648,8 @@ params:
|
||||
pcfg.Sitemap = p.s.conf.Sitemap
|
||||
}
|
||||
|
||||
pcfg.Markup = p.s.ContentSpec.ResolveMarkup(pcfg.Markup)
|
||||
|
||||
if draft != nil && published != nil {
|
||||
pcfg.Draft = *draft
|
||||
p.m.s.Log.Warnf("page %q has both draft and published settings in its frontmatter. Using draft.", p.File().Filename())
|
||||
@@ -684,14 +672,6 @@ params:
|
||||
|
||||
params["iscjklanguage"] = pcfg.IsCJKLanguage
|
||||
|
||||
if err := pcfg.Validate(false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := pcfg.Compile("", false, ext, p.s.Log, p.s.conf.MediaTypes.Config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -747,16 +727,18 @@ func (p *pageMeta) applyDefaultValues() error {
|
||||
(&p.pageConfig.Build).Disable()
|
||||
}
|
||||
|
||||
if p.pageConfig.Content.Markup == "" {
|
||||
if p.pageConfig.Markup == "" {
|
||||
if p.File() != nil {
|
||||
// Fall back to file extension
|
||||
p.pageConfig.Content.Markup = p.s.ContentSpec.ResolveMarkup(p.File().Ext())
|
||||
p.pageConfig.Markup = p.s.ContentSpec.ResolveMarkup(p.File().Ext())
|
||||
}
|
||||
if p.pageConfig.Content.Markup == "" {
|
||||
p.pageConfig.Content.Markup = "markdown"
|
||||
if p.pageConfig.Markup == "" {
|
||||
p.pageConfig.Markup = "markdown"
|
||||
}
|
||||
}
|
||||
|
||||
p.pageConfig.IsGoldmark = p.s.ContentSpec.Converters.IsGoldmark(p.pageConfig.Markup)
|
||||
|
||||
if p.pageConfig.Title == "" && p.f == nil {
|
||||
switch p.Kind() {
|
||||
case kinds.KindHome:
|
||||
|
||||
+37
-36
@@ -15,6 +15,7 @@ package hugolib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
@@ -35,17 +36,21 @@ var pageIDCounter atomic.Uint64
|
||||
|
||||
func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) {
|
||||
m.Staler = &resources.AtomicStaler{}
|
||||
if m.pageMetaParams == nil {
|
||||
m.pageMetaParams = &pageMetaParams{
|
||||
pageConfig: &pagemeta.PageConfig{},
|
||||
if m.pageConfig == nil {
|
||||
m.pageMetaParams = pageMetaParams{
|
||||
pageConfig: &pagemeta.PageConfig{
|
||||
Params: maps.Params{},
|
||||
},
|
||||
}
|
||||
}
|
||||
if m.pageConfig.Params == nil {
|
||||
m.pageConfig.Params = maps.Params{}
|
||||
|
||||
var sourceKey string
|
||||
if m.f != nil {
|
||||
sourceKey = filepath.ToSlash(m.f.Filename())
|
||||
}
|
||||
|
||||
pid := pageIDCounter.Add(1)
|
||||
pi, err := m.parseFrontMatter(h, pid)
|
||||
pi, err := m.parseFrontMatter(h, pid, sourceKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -62,43 +67,30 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) {
|
||||
|
||||
if pcfg.Path != "" {
|
||||
s := m.pageConfig.Path
|
||||
// Paths from content adapters should never have any extension.
|
||||
if pcfg.IsFromContentAdapter || !paths.HasExt(s) {
|
||||
if !paths.HasExt(s) {
|
||||
var (
|
||||
isBranch bool
|
||||
isBranchSet bool
|
||||
ext string = m.pageConfig.ContentMediaType.FirstSuffix.Suffix
|
||||
isBranch bool
|
||||
ext string = "md"
|
||||
)
|
||||
if pcfg.Kind != "" {
|
||||
isBranch = kinds.IsBranch(pcfg.Kind)
|
||||
isBranchSet = true
|
||||
}
|
||||
|
||||
if !pcfg.IsFromContentAdapter {
|
||||
if m.pathInfo != nil {
|
||||
if !isBranchSet {
|
||||
isBranch = m.pathInfo.IsBranchBundle()
|
||||
}
|
||||
if m.pathInfo.Ext() != "" {
|
||||
ext = m.pathInfo.Ext()
|
||||
}
|
||||
} else if m.f != nil {
|
||||
pi := m.f.FileInfo().Meta().PathInfo
|
||||
if !isBranchSet {
|
||||
isBranch = pi.IsBranchBundle()
|
||||
}
|
||||
if pi.Ext() != "" {
|
||||
ext = pi.Ext()
|
||||
}
|
||||
} else if m.pathInfo != nil {
|
||||
isBranch = m.pathInfo.IsBranchBundle()
|
||||
if m.pathInfo.Ext() != "" {
|
||||
ext = m.pathInfo.Ext()
|
||||
}
|
||||
} else if m.f != nil {
|
||||
pi := m.f.FileInfo().Meta().PathInfo
|
||||
isBranch = pi.IsBranchBundle()
|
||||
if pi.Ext() != "" {
|
||||
ext = pi.Ext()
|
||||
}
|
||||
}
|
||||
|
||||
if isBranch {
|
||||
s += "/_index." + ext
|
||||
} else {
|
||||
s += "/index." + ext
|
||||
}
|
||||
|
||||
}
|
||||
m.pathInfo = h.Conf.PathParser().Parse(files.ComponentFolderContent, s)
|
||||
} else if m.pathInfo == nil {
|
||||
@@ -120,13 +112,23 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) {
|
||||
} else if m.f != nil {
|
||||
meta := m.f.FileInfo().Meta()
|
||||
lang = meta.Lang
|
||||
m.s = h.Sites[meta.LangIndex]
|
||||
} else {
|
||||
lang = m.pathInfo.Lang()
|
||||
}
|
||||
if lang == "" {
|
||||
lang = h.Conf.DefaultContentLanguage()
|
||||
}
|
||||
var found bool
|
||||
for _, ss := range h.Sites {
|
||||
if ss.Lang() == lang {
|
||||
m.s = ss
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
m.s = h.resolveSite(lang)
|
||||
|
||||
if m.s == nil {
|
||||
if !found {
|
||||
return nil, fmt.Errorf("no site found for language %q", lang)
|
||||
}
|
||||
}
|
||||
@@ -182,7 +184,6 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) {
|
||||
ResourceNameTitleProvider: m,
|
||||
ResourceParamsProvider: m,
|
||||
PageMetaProvider: m,
|
||||
PageMetaInternalProvider: m,
|
||||
RelatedKeywordsProvider: m,
|
||||
OutputFormatsProvider: page.NopPage,
|
||||
ResourceTypeProvider: pageTypesProvider,
|
||||
|
||||
@@ -25,8 +25,6 @@ import (
|
||||
"github.com/gohugoio/hugo/common/text"
|
||||
"github.com/gohugoio/hugo/common/types/hstring"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/markup"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/gohugoio/hugo/parser/pageparser"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/spf13/cast"
|
||||
@@ -91,8 +89,8 @@ type pageContentOutput struct {
|
||||
// typically included with .RenderShortcodes.
|
||||
otherOutputs map[uint64]*pageContentOutput
|
||||
|
||||
contentRenderedVersion uint32 // Incremented on reset.
|
||||
contentRendered bool // Set on content render.
|
||||
contentRenderedVersion int // Incremented on reset.
|
||||
contentRendered bool // Set on content render.
|
||||
|
||||
// Renders Markdown hooks.
|
||||
renderHooks *renderHooks
|
||||
@@ -264,9 +262,6 @@ func (pco *pageContentOutput) RenderString(ctx context.Context, args ...any) (te
|
||||
if err := mapstructure.WeakDecode(m, &opts); err != nil {
|
||||
return "", fmt.Errorf("failed to decode options: %w", err)
|
||||
}
|
||||
if opts.Markup != "" {
|
||||
opts.Markup = markup.ResolveMarkup(opts.Markup)
|
||||
}
|
||||
}
|
||||
|
||||
contentToRenderv := args[sidx]
|
||||
@@ -288,8 +283,7 @@ func (pco *pageContentOutput) RenderString(ctx context.Context, args ...any) (te
|
||||
}
|
||||
|
||||
conv := pco.po.p.getContentConverter()
|
||||
|
||||
if opts.Markup != "" && opts.Markup != pco.po.p.m.pageConfig.ContentMediaType.SubType {
|
||||
if opts.Markup != "" && opts.Markup != pco.po.p.m.pageConfig.Markup {
|
||||
var err error
|
||||
conv, err = pco.po.p.m.newContentConverter(pco.po.p, opts.Markup)
|
||||
if err != nil {
|
||||
@@ -307,10 +301,7 @@ func (pco *pageContentOutput) RenderString(ctx context.Context, args ...any) (te
|
||||
if pageparser.HasShortcode(contentToRender) {
|
||||
contentToRenderb := []byte(contentToRender)
|
||||
// String contains a shortcode.
|
||||
parseInfo.itemsStep1, err = pageparser.ParseBytes(contentToRenderb, pageparser.Config{
|
||||
NoFrontMatter: true,
|
||||
NoSummaryDivider: true,
|
||||
})
|
||||
parseInfo.itemsStep1, err = pageparser.ParseBytesMain(contentToRenderb, pageparser.Config{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -385,7 +376,7 @@ func (pco *pageContentOutput) RenderString(ctx context.Context, args ...any) (te
|
||||
}
|
||||
|
||||
if opts.Display == "inline" {
|
||||
markup := pco.po.p.m.pageConfig.Content.Markup
|
||||
markup := pco.po.p.m.pageConfig.Markup
|
||||
if opts.Markup != "" {
|
||||
markup = pco.po.p.s.ContentSpec.ResolveMarkup(opts.Markup)
|
||||
}
|
||||
@@ -666,7 +657,7 @@ func splitUserDefinedSummaryAndContent(markup string, c []byte) (summary []byte,
|
||||
|
||||
startTag := "p"
|
||||
switch markup {
|
||||
case media.DefaultContentTypes.AsciiDoc.SubType:
|
||||
case "asciidocext":
|
||||
startTag = "div"
|
||||
}
|
||||
|
||||
|
||||
@@ -693,36 +693,3 @@ draft: true
|
||||
b.AssertFileContent("public/s1-foo/index.html", "/s1-foo/: Pages: /s1-foo/p2/|/s1-foo/s2-foo/|/s1-foo/s2/|$")
|
||||
b.AssertFileContent("public/s1-foo/s2/index.html", "/s1-foo/s2/: Pages: /s1-foo/s2/p3/|$")
|
||||
}
|
||||
|
||||
func TestGetPageContentAdapterBaseIssue12561(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['rss','section','sitemap','taxonomy','term']
|
||||
-- layouts/index.html --
|
||||
Test A: {{ (site.GetPage "/s1/p1").Title }}
|
||||
Test B: {{ (site.GetPage "p1").Title }}
|
||||
Test C: {{ (site.GetPage "/s2/p2").Title }}
|
||||
Test D: {{ (site.GetPage "p2").Title }}
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Title }}
|
||||
-- content/s1/p1.md --
|
||||
---
|
||||
title: p1
|
||||
---
|
||||
-- content/s2/_content.gotmpl --
|
||||
{{ .AddPage (dict "path" "p2" "title" "p2") }}
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileExists("public/s1/p1/index.html", true)
|
||||
b.AssertFileExists("public/s2/p2/index.html", true)
|
||||
b.AssertFileContent("public/index.html",
|
||||
"Test A: p1",
|
||||
"Test B: p1",
|
||||
"Test C: p2",
|
||||
"Test D: p2", // fails
|
||||
)
|
||||
}
|
||||
|
||||
+12
-41
@@ -42,20 +42,18 @@ func newPagesCollector(
|
||||
logger loggers.Logger,
|
||||
infoLogger logg.LevelLogger,
|
||||
m *pageMap,
|
||||
buildConfig *BuildCfg,
|
||||
ids []pathChange,
|
||||
) *pagesCollector {
|
||||
return &pagesCollector{
|
||||
ctx: ctx,
|
||||
h: h,
|
||||
fs: sp.BaseFs.Content.Fs,
|
||||
m: m,
|
||||
sp: sp,
|
||||
logger: logger,
|
||||
infoLogger: infoLogger,
|
||||
buildConfig: buildConfig,
|
||||
ids: ids,
|
||||
seenDirs: make(map[string]bool),
|
||||
ctx: ctx,
|
||||
h: h,
|
||||
fs: sp.BaseFs.Content.Fs,
|
||||
m: m,
|
||||
sp: sp,
|
||||
logger: logger,
|
||||
infoLogger: infoLogger,
|
||||
ids: ids,
|
||||
seenDirs: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +68,6 @@ type pagesCollector struct {
|
||||
|
||||
fs afero.Fs
|
||||
|
||||
buildConfig *BuildCfg
|
||||
|
||||
// List of paths that have changed. Used in partial builds.
|
||||
ids []pathChange
|
||||
seenDirs map[string]bool
|
||||
@@ -86,8 +82,6 @@ func (c *pagesCollector) Collect() (collectErr error) {
|
||||
var (
|
||||
numWorkers = c.h.numWorkers
|
||||
numFilesProcessedTotal atomic.Uint64
|
||||
numPagesProcessedTotal atomic.Uint64
|
||||
numResourcesProcessed atomic.Uint64
|
||||
numFilesProcessedLast uint64
|
||||
fileBatchTimer = time.Now()
|
||||
fileBatchTimerMu sync.Mutex
|
||||
@@ -104,8 +98,6 @@ func (c *pagesCollector) Collect() (collectErr error) {
|
||||
logg.Fields{
|
||||
logg.Field{Name: "files", Value: numFilesProcessedBatch},
|
||||
logg.Field{Name: "files_total", Value: numFilesProcessedTotal.Load()},
|
||||
logg.Field{Name: "pages_total", Value: numPagesProcessedTotal.Load()},
|
||||
logg.Field{Name: "resources_total", Value: numResourcesProcessed.Load()},
|
||||
},
|
||||
"",
|
||||
)
|
||||
@@ -121,13 +113,10 @@ func (c *pagesCollector) Collect() (collectErr error) {
|
||||
c.g = rungroup.Run[hugofs.FileMetaInfo](c.ctx, rungroup.Config[hugofs.FileMetaInfo]{
|
||||
NumWorkers: numWorkers,
|
||||
Handle: func(ctx context.Context, fi hugofs.FileMetaInfo) error {
|
||||
numPages, numResources, err := c.m.AddFi(fi, c.buildConfig)
|
||||
if err != nil {
|
||||
if err := c.m.AddFi(fi); err != nil {
|
||||
return hugofs.AddFileInfoToError(err, fi, c.fs)
|
||||
}
|
||||
numFilesProcessedTotal.Add(1)
|
||||
numPagesProcessedTotal.Add(numPages)
|
||||
numResourcesProcessed.Add(numResources)
|
||||
if numFilesProcessedTotal.Load()%1000 == 0 {
|
||||
logFilesProcessed(false)
|
||||
}
|
||||
@@ -158,7 +147,7 @@ func (c *pagesCollector) Collect() (collectErr error) {
|
||||
false,
|
||||
func(fim hugofs.FileMetaInfo) bool {
|
||||
if fim.IsDir() {
|
||||
return id.isStructuralChange()
|
||||
return true
|
||||
}
|
||||
fimp := fim.Meta().PathInfo
|
||||
if fimp == nil {
|
||||
@@ -171,7 +160,7 @@ func (c *pagesCollector) Collect() (collectErr error) {
|
||||
} else {
|
||||
// We always start from a directory.
|
||||
collectErr = c.collectDir(id.p, id.isDir, func(fim hugofs.FileMetaInfo) bool {
|
||||
if id.isStructuralChange() {
|
||||
if id.delete || id.isDir {
|
||||
if id.isDir && fim.Meta().PathInfo.IsLeafBundle() {
|
||||
return strings.HasPrefix(fim.Meta().PathInfo.Path(), paths.AddTrailingSlash(id.p.Path()))
|
||||
}
|
||||
@@ -254,21 +243,6 @@ func (c *pagesCollector) collectDirDir(path string, root hugofs.FileMetaInfo, in
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
n := 0
|
||||
for _, fi := range readdir {
|
||||
if fi.Meta().PathInfo.IsContentData() {
|
||||
// _content.json
|
||||
// These are not part of any bundle, so just add them directly and remove them from the readdir slice.
|
||||
if err := c.g.Enqueue(fi); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
readdir[n] = fi
|
||||
n++
|
||||
}
|
||||
}
|
||||
readdir = readdir[:n]
|
||||
|
||||
// Pick the first regular file.
|
||||
var first hugofs.FileMetaInfo
|
||||
for _, fi := range readdir {
|
||||
@@ -286,7 +260,6 @@ func (c *pagesCollector) collectDirDir(path string, root hugofs.FileMetaInfo, in
|
||||
|
||||
// Any bundle file will always be first.
|
||||
firstPi := first.Meta().PathInfo
|
||||
|
||||
if firstPi == nil {
|
||||
panic(fmt.Sprintf("collectDirDir: no path info for %q", first.Meta().Filename))
|
||||
}
|
||||
@@ -347,7 +320,6 @@ func (c *pagesCollector) collectDirDir(path string, root hugofs.FileMetaInfo, in
|
||||
Info: root,
|
||||
Fs: c.fs,
|
||||
IgnoreFile: c.h.SourceSpec.IgnoreFile,
|
||||
PathParser: c.h.Conf.PathParser(),
|
||||
HookPre: preHook,
|
||||
HookPost: postHook,
|
||||
WalkFn: wfn,
|
||||
@@ -398,7 +370,6 @@ func (c *pagesCollector) handleBundleLeaf(dir, bundle hugofs.FileMetaInfo, inPat
|
||||
Info: dir,
|
||||
DirEntries: readdir,
|
||||
IgnoreFile: c.h.SourceSpec.IgnoreFile,
|
||||
PathParser: c.h.Conf.PathParser(),
|
||||
WalkFn: walk,
|
||||
})
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ func TestMergeLanguages(t *testing.T) {
|
||||
|
||||
firstNN := nnSite.RegularPages()[0]
|
||||
c.Assert(len(firstNN.Sites()), qt.Equals, 4)
|
||||
c.Assert(firstNN.Sites().Default().Language().Lang, qt.Equals, "en")
|
||||
c.Assert(firstNN.Sites().First().Language().Lang, qt.Equals, "en")
|
||||
|
||||
nnBundle := nnSite.getPageOldVersion("page", "bundle")
|
||||
enBundle := enSite.getPageOldVersion("page", "bundle")
|
||||
|
||||
@@ -1,331 +0,0 @@
|
||||
// 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 pagesfromdata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
"github.com/gohugoio/hugo/resources/page/pagemeta"
|
||||
"github.com/gohugoio/hugo/resources/resource"
|
||||
"github.com/gohugoio/hugo/tpl"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
type PagesFromDataTemplateContext interface {
|
||||
// AddPage adds a new page to the site.
|
||||
// The first return value will always be an empty string.
|
||||
AddPage(any) (string, error)
|
||||
|
||||
// AddResource adds a new resource to the site.
|
||||
// The first return value will always be an empty string.
|
||||
AddResource(any) (string, error)
|
||||
|
||||
// The site to which the pages will be added.
|
||||
Site() page.Site
|
||||
|
||||
// The same template may be executed multiple times for multiple languages.
|
||||
// The Store can be used to store state between these invocations.
|
||||
Store() *maps.Scratch
|
||||
|
||||
// By default, the template will be executed for the language
|
||||
// defined by the _content.gotmpl file (e.g. its mount definition).
|
||||
// This method can be used to activate the template for all languages.
|
||||
// The return value will always be an empty string.
|
||||
EnableAllLanguages() string
|
||||
}
|
||||
|
||||
var _ PagesFromDataTemplateContext = (*pagesFromDataTemplateContext)(nil)
|
||||
|
||||
type pagesFromDataTemplateContext struct {
|
||||
p *PagesFromTemplate
|
||||
}
|
||||
|
||||
func (p *pagesFromDataTemplateContext) toPathMap(v any) (string, map[string]any, error) {
|
||||
m, err := maps.ToStringMapE(v)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
pathv, ok := m["path"]
|
||||
if !ok {
|
||||
return "", nil, fmt.Errorf("path not set")
|
||||
}
|
||||
path, err := cast.ToStringE(pathv)
|
||||
if err != nil || path == "" {
|
||||
return "", nil, fmt.Errorf("invalid path %q", path)
|
||||
}
|
||||
return path, m, nil
|
||||
}
|
||||
|
||||
func (p *pagesFromDataTemplateContext) AddPage(v any) (string, error) {
|
||||
path, m, err := p.toPathMap(v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !p.p.buildState.checkHasChangedAndSetSourceInfo(path, m) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
pd := pagemeta.DefaultPageConfig
|
||||
pd.IsFromContentAdapter = true
|
||||
|
||||
if err := mapstructure.WeakDecode(m, &pd); err != nil {
|
||||
return "", fmt.Errorf("failed to decode page map: %w", err)
|
||||
}
|
||||
|
||||
p.p.buildState.NumPagesAdded++
|
||||
|
||||
if err := pd.Validate(true); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return "", p.p.HandlePage(p.p, &pd)
|
||||
}
|
||||
|
||||
func (p *pagesFromDataTemplateContext) AddResource(v any) (string, error) {
|
||||
path, m, err := p.toPathMap(v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !p.p.buildState.checkHasChangedAndSetSourceInfo(path, m) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
var rd pagemeta.ResourceConfig
|
||||
if err := mapstructure.WeakDecode(m, &rd); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
p.p.buildState.NumResourcesAdded++
|
||||
|
||||
if err := rd.Validate(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return "", p.p.HandleResource(p.p, &rd)
|
||||
}
|
||||
|
||||
func (p *pagesFromDataTemplateContext) Site() page.Site {
|
||||
return p.p.Site
|
||||
}
|
||||
|
||||
func (p *pagesFromDataTemplateContext) Store() *maps.Scratch {
|
||||
return p.p.store
|
||||
}
|
||||
|
||||
func (p *pagesFromDataTemplateContext) EnableAllLanguages() string {
|
||||
p.p.buildState.EnableAllLanguages = true
|
||||
return ""
|
||||
}
|
||||
|
||||
func NewPagesFromTemplate(opts PagesFromTemplateOptions) *PagesFromTemplate {
|
||||
return &PagesFromTemplate{
|
||||
PagesFromTemplateOptions: opts,
|
||||
PagesFromTemplateDeps: opts.DepsFromSite(opts.Site),
|
||||
buildState: &BuildState{
|
||||
sourceInfosCurrent: maps.NewCache[string, *sourceInfo](),
|
||||
},
|
||||
store: maps.NewScratch(),
|
||||
}
|
||||
}
|
||||
|
||||
type PagesFromTemplateOptions struct {
|
||||
Site page.Site
|
||||
DepsFromSite func(page.Site) PagesFromTemplateDeps
|
||||
|
||||
DependencyManager identity.Manager
|
||||
|
||||
Watching bool
|
||||
|
||||
HandlePage func(pt *PagesFromTemplate, p *pagemeta.PageConfig) error
|
||||
HandleResource func(pt *PagesFromTemplate, p *pagemeta.ResourceConfig) error
|
||||
|
||||
GoTmplFi hugofs.FileMetaInfo
|
||||
}
|
||||
|
||||
type PagesFromTemplateDeps struct {
|
||||
TmplFinder tpl.TemplateParseFinder
|
||||
TmplExec tpl.TemplateExecutor
|
||||
}
|
||||
|
||||
var _ resource.Staler = (*PagesFromTemplate)(nil)
|
||||
|
||||
type PagesFromTemplate struct {
|
||||
PagesFromTemplateOptions
|
||||
PagesFromTemplateDeps
|
||||
buildState *BuildState
|
||||
store *maps.Scratch
|
||||
}
|
||||
|
||||
func (b *PagesFromTemplate) AddChange(id identity.Identity) {
|
||||
b.buildState.ChangedIdentities = append(b.buildState.ChangedIdentities, id)
|
||||
}
|
||||
|
||||
func (b *PagesFromTemplate) MarkStale() {
|
||||
b.buildState.StaleVersion++
|
||||
}
|
||||
|
||||
func (b *PagesFromTemplate) StaleVersion() uint32 {
|
||||
return b.buildState.StaleVersion
|
||||
}
|
||||
|
||||
type BuildInfo struct {
|
||||
NumPagesAdded uint64
|
||||
NumResourcesAdded uint64
|
||||
EnableAllLanguages bool
|
||||
ChangedIdentities []identity.Identity
|
||||
DeletedPaths []string
|
||||
Path *paths.Path
|
||||
}
|
||||
|
||||
type BuildState struct {
|
||||
StaleVersion uint32
|
||||
|
||||
EnableAllLanguages bool
|
||||
|
||||
// Paths deleted in the current build.
|
||||
DeletedPaths []string
|
||||
|
||||
// Changed identities in the current build.
|
||||
ChangedIdentities []identity.Identity
|
||||
|
||||
NumPagesAdded uint64
|
||||
NumResourcesAdded uint64
|
||||
|
||||
sourceInfosCurrent *maps.Cache[string, *sourceInfo]
|
||||
sourceInfosPrevious *maps.Cache[string, *sourceInfo]
|
||||
}
|
||||
|
||||
func (b *BuildState) hash(v any) uint64 {
|
||||
return identity.HashUint64(v)
|
||||
}
|
||||
|
||||
func (b *BuildState) checkHasChangedAndSetSourceInfo(changedPath string, v any) bool {
|
||||
h := b.hash(v)
|
||||
si, found := b.sourceInfosPrevious.Get(changedPath)
|
||||
if found {
|
||||
b.sourceInfosCurrent.Set(changedPath, si)
|
||||
if si.hash == h {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
si = &sourceInfo{}
|
||||
b.sourceInfosCurrent.Set(changedPath, si)
|
||||
}
|
||||
si.hash = h
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *BuildState) resolveDeletedPaths() {
|
||||
if b.sourceInfosPrevious == nil {
|
||||
b.DeletedPaths = nil
|
||||
return
|
||||
}
|
||||
var paths []string
|
||||
b.sourceInfosPrevious.ForEeach(func(k string, _ *sourceInfo) {
|
||||
if _, found := b.sourceInfosCurrent.Get(k); !found {
|
||||
paths = append(paths, k)
|
||||
}
|
||||
})
|
||||
|
||||
b.DeletedPaths = paths
|
||||
}
|
||||
|
||||
func (b *BuildState) PrepareNextBuild() {
|
||||
b.sourceInfosPrevious = b.sourceInfosCurrent
|
||||
b.sourceInfosCurrent = maps.NewCache[string, *sourceInfo]()
|
||||
b.StaleVersion = 0
|
||||
b.DeletedPaths = nil
|
||||
b.ChangedIdentities = nil
|
||||
b.NumPagesAdded = 0
|
||||
b.NumResourcesAdded = 0
|
||||
}
|
||||
|
||||
type sourceInfo struct {
|
||||
hash uint64
|
||||
}
|
||||
|
||||
func (p PagesFromTemplate) CloneForSite(s page.Site) *PagesFromTemplate {
|
||||
// We deliberately make them share the same DepenencyManager and Store.
|
||||
p.PagesFromTemplateOptions.Site = s
|
||||
p.PagesFromTemplateDeps = p.PagesFromTemplateOptions.DepsFromSite(s)
|
||||
p.buildState = &BuildState{
|
||||
sourceInfosCurrent: maps.NewCache[string, *sourceInfo](),
|
||||
}
|
||||
return &p
|
||||
}
|
||||
|
||||
func (p PagesFromTemplate) CloneForGoTmpl(fi hugofs.FileMetaInfo) *PagesFromTemplate {
|
||||
p.PagesFromTemplateOptions.GoTmplFi = fi
|
||||
return &p
|
||||
}
|
||||
|
||||
func (p *PagesFromTemplate) GetDependencyManagerForScope(scope int) identity.Manager {
|
||||
return p.DependencyManager
|
||||
}
|
||||
|
||||
func (p *PagesFromTemplate) Execute(ctx context.Context) (BuildInfo, error) {
|
||||
defer func() {
|
||||
p.buildState.PrepareNextBuild()
|
||||
}()
|
||||
|
||||
f, err := p.GoTmplFi.Meta().Open()
|
||||
if err != nil {
|
||||
return BuildInfo{}, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
tmpl, err := p.TmplFinder.Parse(filepath.ToSlash(p.GoTmplFi.Meta().Filename), helpers.ReaderToString(f))
|
||||
if err != nil {
|
||||
return BuildInfo{}, err
|
||||
}
|
||||
|
||||
data := &pagesFromDataTemplateContext{
|
||||
p: p,
|
||||
}
|
||||
|
||||
ctx = tpl.Context.DependencyManagerScopedProvider.Set(ctx, p)
|
||||
|
||||
if err := p.TmplExec.ExecuteWithContext(ctx, tmpl, io.Discard, data); err != nil {
|
||||
return BuildInfo{}, err
|
||||
}
|
||||
|
||||
if p.Watching {
|
||||
p.buildState.resolveDeletedPaths()
|
||||
}
|
||||
|
||||
bi := BuildInfo{
|
||||
NumPagesAdded: p.buildState.NumPagesAdded,
|
||||
NumResourcesAdded: p.buildState.NumResourcesAdded,
|
||||
EnableAllLanguages: p.buildState.EnableAllLanguages,
|
||||
ChangedIdentities: p.buildState.ChangedIdentities,
|
||||
DeletedPaths: p.buildState.DeletedPaths,
|
||||
Path: p.GoTmplFi.Meta().PathInfo,
|
||||
}
|
||||
|
||||
return bi, nil
|
||||
}
|
||||
|
||||
//////////////
|
||||
@@ -1,671 +0,0 @@
|
||||
// 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 pagesfromdata_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
"github.com/gohugoio/hugo/markup/asciidocext"
|
||||
"github.com/gohugoio/hugo/markup/pandoc"
|
||||
"github.com/gohugoio/hugo/markup/rst"
|
||||
)
|
||||
|
||||
const filesPagesFromDataTempleBasic = `
|
||||
-- hugo.toml --
|
||||
disableKinds = ["taxonomy", "term", "rss", "sitemap"]
|
||||
baseURL = "https://example.com"
|
||||
disableLiveReload = true
|
||||
-- assets/a/pixel.png --
|
||||
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
|
||||
-- assets/mydata.yaml --
|
||||
p1: "p1"
|
||||
draft: false
|
||||
-- layouts/partials/get-value.html --
|
||||
{{ $val := "p1" }}
|
||||
{{ return $val }}
|
||||
-- layouts/_default/baseof.html --
|
||||
Baseof:
|
||||
{{ block "main" . }}{{ end }}
|
||||
-- layouts/_default/single.html --
|
||||
{{ define "main" }}
|
||||
Single: {{ .Title }}|{{ .Content }}|Params: {{ .Params.param1 }}|Path: {{ .Path }}|
|
||||
Dates: Date: {{ .Date.Format "2006-01-02" }}|Lastmod: {{ .Lastmod.Format "2006-01-02" }}|PublishDate: {{ .PublishDate.Format "2006-01-02" }}|ExpiryDate: {{ .ExpiryDate.Format "2006-01-02" }}|
|
||||
Len Resources: {{ .Resources | len }}
|
||||
Resources: {{ range .Resources }}RelPermalink: {{ .RelPermalink }}|Name: {{ .Name }}|Title: {{ .Title }}|Params: {{ .Params }}|{{ end }}$
|
||||
{{ with .Resources.Get "featured.png" }}
|
||||
Featured Image: {{ .RelPermalink }}|{{ .Name }}|
|
||||
{{ with .Resize "10x10" }}
|
||||
Resized Featured Image: {{ .RelPermalink }}|{{ .Width }}|
|
||||
{{ end}}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
-- layouts/_default/list.html --
|
||||
List: {{ .Title }}|{{ .Content }}|
|
||||
RegularPagesRecursive: {{ range .RegularPagesRecursive }}{{ .Title }}:{{ .Path }}|{{ end }}$
|
||||
Sections: {{ range .Sections }}{{ .Title }}:{{ .Path }}|{{ end }}$
|
||||
-- content/docs/pfile.md --
|
||||
---
|
||||
title: "pfile"
|
||||
date: 2023-03-01
|
||||
---
|
||||
Pfile Content
|
||||
-- content/docs/_content.gotmpl --
|
||||
{{ $pixel := resources.Get "a/pixel.png" }}
|
||||
{{ $dataResource := resources.Get "mydata.yaml" }}
|
||||
{{ $data := $dataResource | transform.Unmarshal }}
|
||||
{{ $pd := $data.p1 }}
|
||||
{{ $pp := partial "get-value.html" }}
|
||||
{{ $title := printf "%s:%s" $pd $pp }}
|
||||
{{ $date := "2023-03-01" | time.AsTime }}
|
||||
{{ $dates := dict "date" $date }}
|
||||
{{ $contentMarkdown := dict "value" "**Hello World**" "mediaType" "text/markdown" }}
|
||||
{{ $contentMarkdownDefault := dict "value" "**Hello World Default**" }}
|
||||
{{ $contentHTML := dict "value" "<b>Hello World!</b> No **markdown** here." "mediaType" "text/html" }}
|
||||
{{ $.AddPage (dict "kind" "page" "path" "P1" "title" $title "dates" $dates "content" $contentMarkdown "params" (dict "param1" "param1v" ) ) }}
|
||||
{{ $.AddPage (dict "kind" "page" "path" "p2" "title" "p2title" "dates" $dates "content" $contentHTML ) }}
|
||||
{{ $.AddPage (dict "kind" "page" "path" "p3" "title" "p3title" "dates" $dates "content" $contentMarkdownDefault "draft" false ) }}
|
||||
{{ $.AddPage (dict "kind" "page" "path" "p4" "title" "p4title" "dates" $dates "content" $contentMarkdownDefault "draft" $data.draft ) }}
|
||||
|
||||
|
||||
{{ $resourceContent := dict "value" $dataResource }}
|
||||
{{ $.AddResource (dict "path" "p1/data1.yaml" "content" $resourceContent) }}
|
||||
{{ $.AddResource (dict "path" "p1/mytext.txt" "content" (dict "value" "some text") "name" "textresource" "title" "My Text Resource" "params" (dict "param1" "param1v") )}}
|
||||
{{ $.AddResource (dict "path" "p1/sub/mytex2.txt" "content" (dict "value" "some text") "title" "My Text Sub Resource" ) }}
|
||||
{{ $.AddResource (dict "path" "P1/Sub/MyMixCaseText2.txt" "content" (dict "value" "some text") "title" "My Text Sub Mixed Case Path Resource" ) }}
|
||||
{{ $.AddResource (dict "path" "p1/sub/data1.yaml" "content" $resourceContent "title" "Sub data") }}
|
||||
{{ $resourceParams := dict "data2ParaM1" "data2Param1v" }}
|
||||
{{ $.AddResource (dict "path" "p1/data2.yaml" "name" "data2.yaml" "title" "My data 2" "params" $resourceParams "content" $resourceContent) }}
|
||||
{{ $.AddResource (dict "path" "p1/featuredimage.png" "name" "featured.png" "title" "My Featured Image" "params" $resourceParams "content" (dict "value" $pixel ))}}
|
||||
`
|
||||
|
||||
func TestPagesFromGoTmplMisc(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := hugolib.Test(t, filesPagesFromDataTempleBasic)
|
||||
b.AssertPublishDir(`
|
||||
docs/p1/mytext.txt
|
||||
docs/p1/sub/mytex2.tx
|
||||
docs/p1/sub/mymixcasetext2.txt
|
||||
`)
|
||||
|
||||
// Page from markdown file.
|
||||
b.AssertFileContent("public/docs/pfile/index.html", "Dates: Date: 2023-03-01|Lastmod: 2023-03-01|PublishDate: 2023-03-01|ExpiryDate: 0001-01-01|")
|
||||
// Pages from gotmpl.
|
||||
b.AssertFileContent("public/docs/p1/index.html",
|
||||
"Single: p1:p1|",
|
||||
"Path: /docs/p1|",
|
||||
"<strong>Hello World</strong>",
|
||||
"Params: param1v|",
|
||||
"Len Resources: 7",
|
||||
"RelPermalink: /mydata.yaml|Name: data1.yaml|Title: data1.yaml|Params: map[]|",
|
||||
"RelPermalink: /mydata.yaml|Name: data2.yaml|Title: My data 2|Params: map[data2param1:data2Param1v]|",
|
||||
"RelPermalink: /a/pixel.png|Name: featured.png|Title: My Featured Image|Params: map[data2param1:data2Param1v]|",
|
||||
"RelPermalink: /docs/p1/sub/mytex2.txt|Name: sub/mytex2.txt|",
|
||||
"RelPermalink: /docs/p1/sub/mymixcasetext2.txt|Name: sub/mymixcasetext2.txt|",
|
||||
"RelPermalink: /mydata.yaml|Name: sub/data1.yaml|Title: Sub data|Params: map[]|",
|
||||
"Featured Image: /a/pixel.png|featured.png|",
|
||||
"Resized Featured Image: /a/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_10x10_resize_box_3.png|10|",
|
||||
// Resource from string
|
||||
"RelPermalink: /docs/p1/mytext.txt|Name: textresource|Title: My Text Resource|Params: map[param1:param1v]|",
|
||||
// Dates
|
||||
"Dates: Date: 2023-03-01|Lastmod: 2023-03-01|PublishDate: 2023-03-01|ExpiryDate: 0001-01-01|",
|
||||
)
|
||||
b.AssertFileContent("public/docs/p2/index.html", "Single: p2title|", "<b>Hello World!</b> No **markdown** here.")
|
||||
b.AssertFileContent("public/docs/p3/index.html", "<strong>Hello World Default</strong>")
|
||||
}
|
||||
|
||||
func TestPagesFromGoTmplAsciidocAndSimilar(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ["taxonomy", "term", "rss", "sitemap"]
|
||||
baseURL = "https://example.com"
|
||||
[security]
|
||||
[security.exec]
|
||||
allow = ['asciidoctor', 'pandoc','rst2html', 'python']
|
||||
-- layouts/_default/single.html --
|
||||
|Content: {{ .Content }}|Title: {{ .Title }}|Path: {{ .Path }}|
|
||||
-- content/docs/_content.gotmpl --
|
||||
{{ $.AddPage (dict "path" "asciidoc" "content" (dict "value" "Mark my words, #automation is essential#." "mediaType" "text/asciidoc" )) }}
|
||||
{{ $.AddPage (dict "path" "pandoc" "content" (dict "value" "This ~~is deleted text.~~" "mediaType" "text/pandoc" )) }}
|
||||
{{ $.AddPage (dict "path" "rst" "content" (dict "value" "This is *bold*." "mediaType" "text/rst" )) }}
|
||||
{{ $.AddPage (dict "path" "org" "content" (dict "value" "the ability to use +strikethrough+ is a plus" "mediaType" "text/org" )) }}
|
||||
{{ $.AddPage (dict "path" "nocontent" "title" "No Content" ) }}
|
||||
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
if asciidocext.Supports() {
|
||||
b.AssertFileContent("public/docs/asciidoc/index.html",
|
||||
"Mark my words, <mark>automation is essential</mark>",
|
||||
"Path: /docs/asciidoc|",
|
||||
)
|
||||
}
|
||||
if pandoc.Supports() {
|
||||
b.AssertFileContent("public/docs/pandoc/index.html",
|
||||
"This <del>is deleted text.</del>",
|
||||
"Path: /docs/pandoc|",
|
||||
)
|
||||
}
|
||||
|
||||
if rst.Supports() {
|
||||
b.AssertFileContent("public/docs/rst/index.html",
|
||||
"This is <em>bold</em>",
|
||||
"Path: /docs/rst|",
|
||||
)
|
||||
}
|
||||
|
||||
b.AssertFileContent("public/docs/org/index.html",
|
||||
"the ability to use <del>strikethrough</del> is a plus",
|
||||
"Path: /docs/org|",
|
||||
)
|
||||
|
||||
b.AssertFileContent("public/docs/nocontent/index.html", "|Content: |Title: No Content|Path: /docs/nocontent|")
|
||||
}
|
||||
|
||||
func TestPagesFromGoTmplAddPageErrors(t *testing.T) {
|
||||
filesTemplate := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ["taxonomy", "term", "rss", "sitemap"]
|
||||
baseURL = "https://example.com"
|
||||
-- content/docs/_content.gotmpl --
|
||||
{{ $.AddPage DICT }}
|
||||
`
|
||||
|
||||
t.Run("AddPage, missing Path", func(t *testing.T) {
|
||||
files := strings.ReplaceAll(filesTemplate, "DICT", `(dict "kind" "page" "title" "p1")`)
|
||||
b, err := hugolib.TestE(t, files)
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
b.Assert(err.Error(), qt.Contains, "_content.gotmpl:1:4")
|
||||
b.Assert(err.Error(), qt.Contains, "error calling AddPage: path not set")
|
||||
})
|
||||
|
||||
t.Run("AddPage, path starting with slash", func(t *testing.T) {
|
||||
files := strings.ReplaceAll(filesTemplate, "DICT", `(dict "kind" "page" "title" "p1" "path" "/foo")`)
|
||||
b, err := hugolib.TestE(t, files)
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
b.Assert(err.Error(), qt.Contains, `path "/foo" must not start with a /`)
|
||||
})
|
||||
|
||||
t.Run("AddPage, lang set", func(t *testing.T) {
|
||||
files := strings.ReplaceAll(filesTemplate, "DICT", `(dict "kind" "page" "path" "p1" "lang" "en")`)
|
||||
b, err := hugolib.TestE(t, files)
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
b.Assert(err.Error(), qt.Contains, "_content.gotmpl:1:4")
|
||||
b.Assert(err.Error(), qt.Contains, "error calling AddPage: lang must not be set")
|
||||
})
|
||||
|
||||
t.Run("Site methods not ready", func(t *testing.T) {
|
||||
filesTemplate := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ["taxonomy", "term", "rss", "sitemap"]
|
||||
baseURL = "https://example.com"
|
||||
-- content/docs/_content.gotmpl --
|
||||
{{ .Site.METHOD }}
|
||||
`
|
||||
|
||||
for _, method := range []string{"RegularPages", "Pages", "AllPages", "AllRegularPages", "Home", "Sections", "GetPage", "Menus", "MainSections", "Taxonomies"} {
|
||||
t.Run(method, func(t *testing.T) {
|
||||
files := strings.ReplaceAll(filesTemplate, "METHOD", method)
|
||||
b, err := hugolib.TestE(t, files)
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
b.Assert(err.Error(), qt.Contains, fmt.Sprintf("error calling %s: this method cannot be called before the site is fully initialized", method))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestPagesFromGoTmplAddResourceErrors(t *testing.T) {
|
||||
filesTemplate := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ["taxonomy", "term", "rss", "sitemap"]
|
||||
baseURL = "https://example.com"
|
||||
-- content/docs/_content.gotmpl --
|
||||
{{ $.AddResource DICT }}
|
||||
`
|
||||
|
||||
t.Run("missing Path", func(t *testing.T) {
|
||||
files := strings.ReplaceAll(filesTemplate, "DICT", `(dict "name" "r1")`)
|
||||
b, err := hugolib.TestE(t, files)
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
b.Assert(err.Error(), qt.Contains, "error calling AddResource: path not set")
|
||||
})
|
||||
}
|
||||
|
||||
func TestPagesFromGoTmplEditGoTmpl(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := hugolib.TestRunning(t, filesPagesFromDataTempleBasic)
|
||||
b.EditFileReplaceAll("content/docs/_content.gotmpl", `"title" "p2title"`, `"title" "p2titleedited"`).Build()
|
||||
b.AssertFileContent("public/docs/p2/index.html", "Single: p2titleedited|")
|
||||
b.AssertFileContent("public/docs/index.html", "p2titleedited")
|
||||
}
|
||||
|
||||
func TestPagesFromGoTmplEditDataResource(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := hugolib.TestRunning(t, filesPagesFromDataTempleBasic)
|
||||
b.AssertRenderCountPage(7)
|
||||
b.EditFileReplaceAll("assets/mydata.yaml", "p1: \"p1\"", "p1: \"p1edited\"").Build()
|
||||
b.AssertFileContent("public/docs/p1/index.html", "Single: p1edited:p1|")
|
||||
b.AssertFileContent("public/docs/index.html", "p1edited")
|
||||
b.AssertRenderCountPage(3)
|
||||
}
|
||||
|
||||
func TestPagesFromGoTmplEditPartial(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := hugolib.TestRunning(t, filesPagesFromDataTempleBasic)
|
||||
b.EditFileReplaceAll("layouts/partials/get-value.html", "p1", "p1edited").Build()
|
||||
b.AssertFileContent("public/docs/p1/index.html", "Single: p1:p1edited|")
|
||||
b.AssertFileContent("public/docs/index.html", "p1edited")
|
||||
}
|
||||
|
||||
func TestPagesFromGoTmplRemovePage(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := hugolib.TestRunning(t, filesPagesFromDataTempleBasic)
|
||||
b.EditFileReplaceAll("content/docs/_content.gotmpl", `{{ $.AddPage (dict "kind" "page" "path" "p2" "title" "p2title" "dates" $dates "content" $contentHTML ) }}`, "").Build()
|
||||
b.AssertFileContent("public/index.html", "RegularPagesRecursive: p1:p1:/docs/p1|p3title:/docs/p3|p4title:/docs/p4|pfile:/docs/pfile|$")
|
||||
}
|
||||
|
||||
func TestPagesFromGoTmplDraftPage(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := hugolib.TestRunning(t, filesPagesFromDataTempleBasic)
|
||||
b.EditFileReplaceAll("content/docs/_content.gotmpl", `"draft" false`, `"draft" true`).Build()
|
||||
b.AssertFileContent("public/index.html", "RegularPagesRecursive: p1:p1:/docs/p1|p2title:/docs/p2|p4title:/docs/p4|pfile:/docs/pfile|$")
|
||||
}
|
||||
|
||||
func TestPagesFromGoTmplDraftFlagFromResource(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := hugolib.TestRunning(t, filesPagesFromDataTempleBasic)
|
||||
b.EditFileReplaceAll("assets/mydata.yaml", `draft: false`, `draft: true`).Build()
|
||||
b.AssertFileContent("public/index.html", "RegularPagesRecursive: p1:p1:/docs/p1|p2title:/docs/p2|p3title:/docs/p3|pfile:/docs/pfile|$")
|
||||
b.EditFileReplaceAll("assets/mydata.yaml", `draft: true`, `draft: false`).Build()
|
||||
b.AssertFileContent("public/index.html", "RegularPagesRecursive: p1:p1:/docs/p1|p2title:/docs/p2|p3title:/docs/p3|p4title:/docs/p4|pfile:/docs/pfile|$")
|
||||
}
|
||||
|
||||
func TestPagesFromGoTmplMovePage(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := hugolib.TestRunning(t, filesPagesFromDataTempleBasic)
|
||||
b.AssertFileContent("public/index.html", "RegularPagesRecursive: p1:p1:/docs/p1|p2title:/docs/p2|p3title:/docs/p3|p4title:/docs/p4|pfile:/docs/pfile|$")
|
||||
b.EditFileReplaceAll("content/docs/_content.gotmpl", `"path" "p2"`, `"path" "p2moved"`).Build()
|
||||
b.AssertFileContent("public/index.html", "RegularPagesRecursive: p1:p1:/docs/p1|p2title:/docs/p2moved|p3title:/docs/p3|p4title:/docs/p4|pfile:/docs/pfile|$")
|
||||
}
|
||||
|
||||
func TestPagesFromGoTmplRemoveGoTmpl(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := hugolib.TestRunning(t, filesPagesFromDataTempleBasic)
|
||||
b.AssertFileContent("public/index.html",
|
||||
"RegularPagesRecursive: p1:p1:/docs/p1|p2title:/docs/p2|p3title:/docs/p3|p4title:/docs/p4|pfile:/docs/pfile|$",
|
||||
"Sections: Docs:/docs|",
|
||||
)
|
||||
b.AssertFileContent("public/docs/index.html", "RegularPagesRecursive: p1:p1:/docs/p1|p2title:/docs/p2|p3title:/docs/p3|p4title:/docs/p4|pfile:/docs/pfile|$")
|
||||
b.RemoveFiles("content/docs/_content.gotmpl").Build()
|
||||
// One regular page left.
|
||||
b.AssertFileContent("public/index.html",
|
||||
"RegularPagesRecursive: pfile:/docs/pfile|$",
|
||||
"Sections: Docs:/docs|",
|
||||
)
|
||||
b.AssertFileContent("public/docs/index.html", "RegularPagesRecursive: pfile:/docs/pfile|$")
|
||||
}
|
||||
|
||||
func TestPagesFromGoTmplLanguagePerFile(t *testing.T) {
|
||||
filesTemplate := `
|
||||
-- hugo.toml --
|
||||
defaultContentLanguage = "en"
|
||||
defaultContentLanguageInSubdir = true
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
title = "Title"
|
||||
[languages.fr]
|
||||
weight = 2
|
||||
title = "Titre"
|
||||
disabled = DISABLE
|
||||
-- layouts/_default/single.html --
|
||||
Single: {{ .Title }}|{{ .Content }}|
|
||||
-- content/docs/_content.gotmpl --
|
||||
{{ $.AddPage (dict "kind" "page" "path" "p1" "title" "Title" ) }}
|
||||
-- content/docs/_content.fr.gotmpl --
|
||||
{{ $.AddPage (dict "kind" "page" "path" "p1" "title" "Titre" ) }}
|
||||
`
|
||||
|
||||
for _, disable := range []bool{false, true} {
|
||||
t.Run(fmt.Sprintf("disable=%t", disable), func(t *testing.T) {
|
||||
b := hugolib.Test(t, strings.ReplaceAll(filesTemplate, "DISABLE", fmt.Sprintf("%t", disable)))
|
||||
b.AssertFileContent("public/en/docs/p1/index.html", "Single: Title||")
|
||||
b.AssertFileExists("public/fr/docs/p1/index.html", !disable)
|
||||
if !disable {
|
||||
b.AssertFileContent("public/fr/docs/p1/index.html", "Single: Titre||")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPagesFromGoTmplEnableAllLanguages(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
filesTemplate := `
|
||||
-- hugo.toml --
|
||||
defaultContentLanguage = "en"
|
||||
defaultContentLanguageInSubdir = true
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
title = "Title"
|
||||
[languages.fr]
|
||||
title = "Titre"
|
||||
weight = 2
|
||||
disabled = DISABLE
|
||||
-- i18n/en.yaml --
|
||||
title: Title
|
||||
-- i18n/fr.yaml --
|
||||
title: Titre
|
||||
-- content/docs/_content.gotmpl --
|
||||
{{ .EnableAllLanguages }}
|
||||
{{ $titleFromStore := .Store.Get "title" }}
|
||||
{{ if not $titleFromStore }}
|
||||
{{ $titleFromStore = "notfound"}}
|
||||
{{ .Store.Set "title" site.Title }}
|
||||
{{ end }}
|
||||
{{ $title := printf "%s:%s:%s" site.Title (i18n "title") $titleFromStore }}
|
||||
{{ $.AddPage (dict "kind" "page" "path" "p1" "title" $title ) }}
|
||||
-- layouts/_default/single.html --
|
||||
Single: {{ .Title }}|{{ .Content }}|
|
||||
|
||||
`
|
||||
|
||||
for _, disable := range []bool{false, true} {
|
||||
t.Run(fmt.Sprintf("disable=%t", disable), func(t *testing.T) {
|
||||
b := hugolib.Test(t, strings.ReplaceAll(filesTemplate, "DISABLE", fmt.Sprintf("%t", disable)))
|
||||
b.AssertFileExists("public/fr/docs/p1/index.html", !disable)
|
||||
if !disable {
|
||||
b.AssertFileContent("public/en/docs/p1/index.html", "Single: Title:Title:notfound||")
|
||||
b.AssertFileContent("public/fr/docs/p1/index.html", "Single: Titre:Titre:Title||")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPagesFromGoTmplMarkdownify(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ["taxonomy", "term", "rss", "sitemap"]
|
||||
baseURL = "https://example.com"
|
||||
-- layouts/_default/single.html --
|
||||
|Content: {{ .Content }}|Title: {{ .Title }}|Path: {{ .Path }}|
|
||||
-- content/docs/_content.gotmpl --
|
||||
{{ $content := "**Hello World**" | markdownify }}
|
||||
{{ $.AddPage (dict "path" "p1" "content" (dict "value" $content "mediaType" "text/html" )) }}
|
||||
`
|
||||
|
||||
b, err := hugolib.TestE(t, files)
|
||||
|
||||
// This currently fails. We should fix this, but that is not a trivial task, so do it later.
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
b.Assert(err.Error(), qt.Contains, "error calling markdownify: this method cannot be called before the site is fully initialized")
|
||||
}
|
||||
|
||||
func TestPagesFromGoTmplResourceWithoutExtensionWithMediaTypeProvided(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ["taxonomy", "term", "rss", "sitemap"]
|
||||
baseURL = "https://example.com"
|
||||
-- layouts/_default/single.html --
|
||||
|Content: {{ .Content }}|Title: {{ .Title }}|Path: {{ .Path }}|
|
||||
{{ range .Resources }}
|
||||
|RelPermalink: {{ .RelPermalink }}|Name: {{ .Name }}|Title: {{ .Title }}|Params: {{ .Params }}|MediaType: {{ .MediaType }}|
|
||||
{{ end }}
|
||||
-- content/docs/_content.gotmpl --
|
||||
{{ $.AddPage (dict "path" "p1" "content" (dict "value" "**Hello World**" "mediaType" "text/markdown" )) }}
|
||||
{{ $.AddResource (dict "path" "p1/myresource" "content" (dict "value" "abcde" "mediaType" "text/plain" )) }}
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/docs/p1/index.html", "RelPermalink: /docs/p1/myresource|Name: myresource|Title: myresource|Params: map[]|MediaType: text/plain|")
|
||||
}
|
||||
|
||||
func TestPagesFromGoTmplCascade(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ["taxonomy", "term", "rss", "sitemap"]
|
||||
baseURL = "https://example.com"
|
||||
-- layouts/_default/single.html --
|
||||
|Content: {{ .Content }}|Title: {{ .Title }}|Path: {{ .Path }}|Params: {{ .Params }}|
|
||||
-- content/_content.gotmpl --
|
||||
{{ $cascade := dict "params" (dict "cascadeparam1" "cascadeparam1value" ) }}
|
||||
{{ $.AddPage (dict "path" "docs" "kind" "section" "cascade" $cascade ) }}
|
||||
{{ $.AddPage (dict "path" "docs/p1" "content" (dict "value" "**Hello World**" "mediaType" "text/markdown" )) }}
|
||||
|
||||
`
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/docs/p1/index.html", "|Path: /docs/p1|Params: map[cascadeparam1:cascadeparam1value")
|
||||
}
|
||||
|
||||
func TestPagesFromGoBuildOptions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ["taxonomy", "term", "rss", "sitemap"]
|
||||
baseURL = "https://example.com"
|
||||
-- layouts/_default/single.html --
|
||||
|Content: {{ .Content }}|Title: {{ .Title }}|Path: {{ .Path }}|Params: {{ .Params }}|
|
||||
-- content/_content.gotmpl --
|
||||
{{ $.AddPage (dict "path" "docs/p1" "content" (dict "value" "**Hello World**" "mediaType" "text/markdown" )) }}
|
||||
{{ $never := dict "list" "never" "publishResources" false "render" "never" }}
|
||||
{{ $.AddPage (dict "path" "docs/p2" "content" (dict "value" "**Hello World**" "mediaType" "text/markdown" ) "build" $never ) }}
|
||||
|
||||
|
||||
`
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileExists("public/docs/p1/index.html", true)
|
||||
b.AssertFileExists("public/docs/p2/index.html", false)
|
||||
}
|
||||
|
||||
func TestPagesFromGoPathsWithDotsIssue12493(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','section','rss','sitemap','taxonomy','term']
|
||||
-- content/_content.gotmpl --
|
||||
{{ .AddPage (dict "path" "s-1.2.3/p-4.5.6" "title" "p-4.5.6") }}
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Title }}
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileExists("public/s-1.2.3/p-4.5.6/index.html", true)
|
||||
}
|
||||
|
||||
func TestPagesFromGoParamsIssue12497(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','section','rss','sitemap','taxonomy','term']
|
||||
-- content/_content.gotmpl --
|
||||
{{ .AddPage (dict "path" "p1" "title" "p1" "params" (dict "paraM1" "param1v" )) }}
|
||||
{{ .AddResource (dict "path" "p1/data1.yaml" "content" (dict "value" "data1" ) "params" (dict "paraM1" "param1v" )) }}
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Title }}|{{ .Params.paraM1 }}
|
||||
{{ range .Resources }}
|
||||
{{ .Name }}|{{ .Params.paraM1 }}
|
||||
{{ end }}
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/p1/index.html",
|
||||
"p1|param1v",
|
||||
"data1.yaml|param1v",
|
||||
)
|
||||
}
|
||||
|
||||
func TestPagesFromGoTmplPathWarningsPathPage(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableKinds = ['home','section','rss','sitemap','taxonomy','term']
|
||||
printPathWarnings = true
|
||||
-- content/_content.gotmpl --
|
||||
{{ .AddPage (dict "path" "p1" "title" "p1" ) }}
|
||||
{{ .AddPage (dict "path" "p2" "title" "p2" ) }}
|
||||
-- content/p1.md --
|
||||
---
|
||||
title: "p1"
|
||||
---
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Title }}|
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files, hugolib.TestOptWarn())
|
||||
|
||||
b.AssertFileContent("public/p1/index.html", "p1|")
|
||||
|
||||
b.AssertLogContains("Duplicate content path")
|
||||
|
||||
files = strings.ReplaceAll(files, `"path" "p1"`, `"path" "p1new"`)
|
||||
|
||||
b = hugolib.Test(t, files, hugolib.TestOptWarn())
|
||||
|
||||
b.AssertLogNotContains("WARN")
|
||||
}
|
||||
|
||||
func TestPagesFromGoTmplPathWarningsPathResource(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableKinds = ['home','section','rss','sitemap','taxonomy','term']
|
||||
printPathWarnings = true
|
||||
-- content/_content.gotmpl --
|
||||
{{ .AddResource (dict "path" "p1/data1.yaml" "content" (dict "value" "data1" ) ) }}
|
||||
{{ .AddResource (dict "path" "p1/data2.yaml" "content" (dict "value" "data2" ) ) }}
|
||||
|
||||
-- content/p1/index.md --
|
||||
---
|
||||
title: "p1"
|
||||
---
|
||||
-- content/p1/data1.yaml --
|
||||
value: data1
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Title }}|
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files, hugolib.TestOptWarn())
|
||||
|
||||
b.AssertFileContent("public/p1/index.html", "p1|")
|
||||
|
||||
b.AssertLogContains("Duplicate resource path")
|
||||
|
||||
files = strings.ReplaceAll(files, `"path" "p1/data1.yaml"`, `"path" "p1/data1new.yaml"`)
|
||||
|
||||
b = hugolib.Test(t, files, hugolib.TestOptWarn())
|
||||
|
||||
b.AssertLogNotContains("WARN")
|
||||
}
|
||||
|
||||
func TestPagesFromGoTmplShortcodeNoPreceddingCharacterIssue12544(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
|
||||
-- content/_content.gotmpl --
|
||||
{{ $content := dict "mediaType" "text/html" "value" "x{{< sc >}}" }}
|
||||
{{ .AddPage (dict "content" $content "path" "a") }}
|
||||
|
||||
{{ $content := dict "mediaType" "text/html" "value" "{{< sc >}}" }}
|
||||
{{ .AddPage (dict "content" $content "path" "b") }}
|
||||
-- layouts/_default/single.html --
|
||||
|{{ .Content }}|
|
||||
-- layouts/shortcodes/sc.html --
|
||||
foo
|
||||
{{- /**/ -}}
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/a/index.html", "|xfoo|")
|
||||
b.AssertFileContent("public/b/index.html", "|foo|") // fails
|
||||
}
|
||||
|
||||
func TestPagesFromGoTmplMenus(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['rss','section','sitemap','taxonomy','term']
|
||||
|
||||
[menus]
|
||||
[[menus.main]]
|
||||
name = "Main"
|
||||
[[menus.footer]]
|
||||
name = "Footer"
|
||||
-- content/_content.gotmpl --
|
||||
{{ .AddPage (dict "path" "p1" "title" "p1" "menus" "main" ) }}
|
||||
{{ .AddPage (dict "path" "p2" "title" "p2" "menus" (slice "main" "footer")) }}
|
||||
-- layouts/index.html --
|
||||
Main: {{ range index site.Menus.main }}{{ .Name }}|{{ end }}|
|
||||
Footer: {{ range index site.Menus.footer }}{{ .Name }}|{{ end }}|
|
||||
|
||||
`
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/index.html",
|
||||
"Main: Main|p1|p2||",
|
||||
"Footer: Footer|p2||",
|
||||
)
|
||||
}
|
||||
|
||||
func TestPagesFromGoTmplMore(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
|
||||
[markup.goldmark.renderer]
|
||||
unsafe = true
|
||||
-- content/s1/_content.gotmpl --
|
||||
{{ $page := dict
|
||||
"content" (dict "mediaType" "text/markdown" "value" "aaa <!--more--> bbb")
|
||||
"title" "p1"
|
||||
"path" "p1"
|
||||
}}
|
||||
{{ .AddPage $page }}
|
||||
-- layouts/_default/single.html --
|
||||
summary: {{ .Summary }}|content: {{ .Content}}
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/s1/p1/index.html",
|
||||
"<p>aaa</p>|content: <p>aaa</p>\n<p>bbb</p>",
|
||||
)
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
// 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 pagesfromdata
|
||||
|
||||
import "testing"
|
||||
|
||||
func BenchmarkHash(b *testing.B) {
|
||||
m := map[string]any{
|
||||
"foo": "bar",
|
||||
"bar": "foo",
|
||||
"stringSlice": []any{"a", "b", "c"},
|
||||
"intSlice": []any{1, 2, 3},
|
||||
"largeText": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit.",
|
||||
}
|
||||
|
||||
bs := BuildState{}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
bs.hash(m)
|
||||
}
|
||||
}
|
||||
+17
-81
@@ -53,11 +53,6 @@ title: "Home"
|
||||
Home Content.
|
||||
-- content/hometext.txt --
|
||||
Home Text Content.
|
||||
-- content/myothersection/myothersectionpage.md --
|
||||
---
|
||||
title: "myothersectionpage"
|
||||
---
|
||||
myothersectionpage Content.
|
||||
-- layouts/_default/single.html --
|
||||
Single: {{ .Title }}|{{ .Content }}$
|
||||
Resources: {{ range $i, $e := .Resources }}{{ $i }}:{{ .RelPermalink }}|{{ .Content }}|{{ end }}$
|
||||
@@ -126,23 +121,14 @@ func TestRebuildEditTextFileInBranchBundle(t *testing.T) {
|
||||
b.AssertRenderCountContent(1)
|
||||
}
|
||||
|
||||
func testRebuildBothWatchingAndRunning(t *testing.T, files string, withB func(b *IntegrationTestBuilder)) {
|
||||
t.Helper()
|
||||
for _, opt := range []TestOpt{TestOptWatching(), TestOptRunning()} {
|
||||
b := Test(t, files, opt)
|
||||
withB(b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRebuildRenameTextFileInLeafBundle(t *testing.T) {
|
||||
testRebuildBothWatchingAndRunning(t, rebuildFilesSimple, func(b *IntegrationTestBuilder) {
|
||||
b.AssertFileContent("public/mysection/mysectionbundle/index.html", "My Section Bundle Text 2 Content.", "Len Resources: 2|")
|
||||
b := TestRunning(t, rebuildFilesSimple)
|
||||
b.AssertFileContent("public/mysection/mysectionbundle/index.html", "My Section Bundle Text 2 Content.", "Len Resources: 2|")
|
||||
|
||||
b.RenameFile("content/mysection/mysectionbundle/mysectionbundletext.txt", "content/mysection/mysectionbundle/mysectionbundletext2.txt").Build()
|
||||
b.AssertFileContent("public/mysection/mysectionbundle/index.html", "mysectionbundletext2", "My Section Bundle Text 2 Content.", "Len Resources: 2|")
|
||||
b.AssertRenderCountPage(5)
|
||||
b.AssertRenderCountContent(6)
|
||||
})
|
||||
b.RenameFile("content/mysection/mysectionbundle/mysectionbundletext.txt", "content/mysection/mysectionbundle/mysectionbundletext2.txt").Build()
|
||||
b.AssertFileContent("public/mysection/mysectionbundle/index.html", "mysectionbundletext2", "My Section Bundle Text 2 Content.", "Len Resources: 2|")
|
||||
b.AssertRenderCountPage(3)
|
||||
b.AssertRenderCountContent(3)
|
||||
}
|
||||
|
||||
func TestRebuilEditContentFileInLeafBundle(t *testing.T) {
|
||||
@@ -152,19 +138,6 @@ func TestRebuilEditContentFileInLeafBundle(t *testing.T) {
|
||||
b.AssertFileContent("public/mysection/mysectionbundle/index.html", "My Section Bundle Content Content Edited.")
|
||||
}
|
||||
|
||||
func TestRebuilEditContentFileThenAnother(t *testing.T) {
|
||||
b := TestRunning(t, rebuildFilesSimple)
|
||||
b.EditFileReplaceAll("content/mysection/mysectionbundle/mysectionbundlecontent.md", "Content Content.", "Content Content Edited.").Build()
|
||||
b.AssertFileContent("public/mysection/mysectionbundle/index.html", "My Section Bundle Content Content Edited.")
|
||||
b.AssertRenderCountPage(1)
|
||||
b.AssertRenderCountContent(2)
|
||||
|
||||
b.EditFileReplaceAll("content/myothersection/myothersectionpage.md", "myothersectionpage Content.", "myothersectionpage Content Edited.").Build()
|
||||
b.AssertFileContent("public/myothersection/myothersectionpage/index.html", "myothersectionpage Content Edited")
|
||||
b.AssertRenderCountPage(1)
|
||||
b.AssertRenderCountContent(1)
|
||||
}
|
||||
|
||||
func TestRebuildRenameTextFileInBranchBundle(t *testing.T) {
|
||||
b := TestRunning(t, rebuildFilesSimple)
|
||||
b.AssertFileContent("public/mysection/index.html", "My Section")
|
||||
@@ -181,7 +154,7 @@ func TestRebuildRenameTextFileInHomeBundle(t *testing.T) {
|
||||
|
||||
b.RenameFile("content/hometext.txt", "content/hometext2.txt").Build()
|
||||
b.AssertFileContent("public/index.html", "hometext2", "Home Text Content.")
|
||||
b.AssertRenderCountPage(3)
|
||||
b.AssertRenderCountPage(2)
|
||||
}
|
||||
|
||||
func TestRebuildRenameDirectoryWithLeafBundle(t *testing.T) {
|
||||
@@ -197,7 +170,7 @@ func TestRebuildRenameDirectoryWithBranchBundle(t *testing.T) {
|
||||
b.AssertFileContent("public/mysectionrenamed/index.html", "My Section")
|
||||
b.AssertFileContent("public/mysectionrenamed/mysectionbundle/index.html", "My Section Bundle")
|
||||
b.AssertFileContent("public/mysectionrenamed/mysectionbundle/mysectionbundletext.txt", "My Section Bundle Text 2 Content.")
|
||||
b.AssertRenderCountPage(3)
|
||||
b.AssertRenderCountPage(2)
|
||||
}
|
||||
|
||||
func TestRebuildRenameDirectoryWithRegularPageUsedInHome(t *testing.T) {
|
||||
@@ -296,7 +269,7 @@ func TestRebuildRenameDirectoryWithBranchBundleFastRender(t *testing.T) {
|
||||
b.AssertFileContent("public/mysectionrenamed/index.html", "My Section")
|
||||
b.AssertFileContent("public/mysectionrenamed/mysectionbundle/index.html", "My Section Bundle")
|
||||
b.AssertFileContent("public/mysectionrenamed/mysectionbundle/mysectionbundletext.txt", "My Section Bundle Text 2 Content.")
|
||||
b.AssertRenderCountPage(3)
|
||||
b.AssertRenderCountPage(2)
|
||||
}
|
||||
|
||||
func TestRebuilErrorRecovery(t *testing.T) {
|
||||
@@ -394,6 +367,8 @@ My short.
|
||||
}
|
||||
|
||||
func TestRebuildBaseof(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
title = "Hugo Site"
|
||||
@@ -408,13 +383,12 @@ Baseof: {{ .Title }}|
|
||||
Home: {{ .Title }}|{{ .Content }}|
|
||||
{{ end }}
|
||||
`
|
||||
testRebuildBothWatchingAndRunning(t, files, func(b *IntegrationTestBuilder) {
|
||||
b.AssertFileContent("public/index.html", "Baseof: Hugo Site|", "Home: Hugo Site||")
|
||||
b.EditFileReplaceFunc("layouts/_default/baseof.html", func(s string) string {
|
||||
return strings.Replace(s, "Baseof", "Baseof Edited", 1)
|
||||
}).Build()
|
||||
b.AssertFileContent("public/index.html", "Baseof Edited: Hugo Site|", "Home: Hugo Site||")
|
||||
})
|
||||
b := Test(t, files, TestOptRunning())
|
||||
b.AssertFileContent("public/index.html", "Baseof: Hugo Site|", "Home: Hugo Site||")
|
||||
b.EditFileReplaceFunc("layouts/_default/baseof.html", func(s string) string {
|
||||
return strings.Replace(s, "Baseof", "Baseof Edited", 1)
|
||||
}).Build()
|
||||
b.AssertFileContent("public/index.html", "Baseof Edited: Hugo Site|", "Home: Hugo Site||")
|
||||
}
|
||||
|
||||
func TestRebuildSingleWithBaseof(t *testing.T) {
|
||||
@@ -1603,41 +1577,3 @@ title: p1
|
||||
b.AddFiles("content/p2.md", "---\ntitle: p2\n---").Build()
|
||||
b.AssertFileContent("public/index.html", "p1|p2|") // this test passes, which doesn't match reality
|
||||
}
|
||||
|
||||
func TestRebuildHomeThenPageIssue12436(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableKinds = ['sitemap','taxonomy','term']
|
||||
disableLiveReload = true
|
||||
-- layouts/_default/list.html --
|
||||
{{ .Content }}
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Content }}
|
||||
-- content/_index.md --
|
||||
---
|
||||
title: home
|
||||
---
|
||||
home-content|
|
||||
-- content/p1/index.md --
|
||||
---
|
||||
title: p1
|
||||
---
|
||||
p1-content|
|
||||
`
|
||||
|
||||
b := TestRunning(t, files)
|
||||
|
||||
b.AssertFileContent("public/index.html", "home-content|")
|
||||
b.AssertFileContent("public/p1/index.html", "p1-content|")
|
||||
b.AssertRenderCountPage(3)
|
||||
|
||||
b.EditFileReplaceAll("content/_index.md", "home-content", "home-content-foo").Build()
|
||||
b.AssertFileContent("public/index.html", "home-content-foo")
|
||||
b.AssertRenderCountPage(2) // Home page rss + html
|
||||
|
||||
b.EditFileReplaceAll("content/p1/index.md", "p1-content", "p1-content-foo").Build()
|
||||
b.AssertFileContent("public/p1/index.html", "p1-content-foo")
|
||||
}
|
||||
|
||||
@@ -201,43 +201,6 @@ Myshort Original.
|
||||
b.AssertFileContent("public/p1/index.html", "Edited")
|
||||
}
|
||||
|
||||
func TestRenderShortcodesEditSectionContentWithShortcodeInIncludedPageIssue12458(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableLiveReload = true
|
||||
disableKinds = ["home", "taxonomy", "term", "rss", "sitemap", "robotsTXT", "404"]
|
||||
-- content/mysection/_index.md --
|
||||
---
|
||||
title: "My Section"
|
||||
---
|
||||
## p1-h1
|
||||
{{% include "p2" %}}
|
||||
-- content/mysection/p2.md --
|
||||
---
|
||||
title: "p2"
|
||||
---
|
||||
### Original
|
||||
{{% myshort %}}
|
||||
-- layouts/shortcodes/include.html --
|
||||
{{ $p := .Page.GetPage (.Get 0) }}
|
||||
{{ $p.RenderShortcodes }}
|
||||
-- layouts/shortcodes/myshort.html --
|
||||
Myshort Original.
|
||||
-- layouts/_default/list.html --
|
||||
{{ .Content }}
|
||||
|
||||
|
||||
|
||||
`
|
||||
b := TestRunning(t, files)
|
||||
|
||||
b.AssertFileContent("public/mysection/index.html", "p1-h1")
|
||||
b.EditFileReplaceAll("content/mysection/_index.md", "p1-h1", "p1-h1 Edited").Build()
|
||||
b.AssertFileContent("public/mysection/index.html", "p1-h1 Edited")
|
||||
}
|
||||
|
||||
func TestRenderShortcodesNestedPageContextIssue12356(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -321,7 +321,7 @@ func prepareShortcode(
|
||||
|
||||
// Allow the caller to delay the rendering of the shortcode if needed.
|
||||
var fn shortcodeRenderFunc = func(ctx context.Context) ([]byte, bool, error) {
|
||||
if p.m.pageConfig.ContentMediaType.IsMarkdown() && sc.doMarkup {
|
||||
if p.m.pageConfig.IsGoldmark && sc.doMarkup {
|
||||
// Signal downwards that the content rendered will be
|
||||
// parsed and rendered by Goldmark.
|
||||
ctx = tpl.Context.IsInGoldmark.Set(ctx, true)
|
||||
@@ -449,7 +449,7 @@ func doRenderShortcode(
|
||||
// unchanged.
|
||||
// 2 If inner does not have a newline, strip the wrapping <p> block and
|
||||
// the newline.
|
||||
switch p.m.pageConfig.Content.Markup {
|
||||
switch p.m.pageConfig.Markup {
|
||||
case "", "markdown":
|
||||
if match, _ := regexp.MatchString(innerNewlineRegexp, inner); !match {
|
||||
cleaner, err := regexp.Compile(innerCleanupRegexp)
|
||||
|
||||
+2
-27
@@ -62,7 +62,6 @@ import (
|
||||
)
|
||||
|
||||
func (s *Site) Taxonomies() page.TaxonomyList {
|
||||
s.checkReady()
|
||||
s.init.taxonomies.Do(context.Background())
|
||||
return s.taxonomies
|
||||
}
|
||||
@@ -205,7 +204,6 @@ type siteRenderingContext struct {
|
||||
}
|
||||
|
||||
func (s *Site) Menus() navigation.Menus {
|
||||
s.checkReady()
|
||||
s.init.menus.Do(context.Background())
|
||||
return s.menus
|
||||
}
|
||||
@@ -374,33 +372,19 @@ func (s *Site) watching() bool {
|
||||
type whatChanged struct {
|
||||
mu sync.Mutex
|
||||
|
||||
needsPagesAssembly bool
|
||||
identitySet identity.Identities
|
||||
contentChanged bool
|
||||
identitySet identity.Identities
|
||||
}
|
||||
|
||||
func (w *whatChanged) Add(ids ...identity.Identity) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
if w.identitySet == nil {
|
||||
w.identitySet = make(identity.Identities)
|
||||
}
|
||||
|
||||
for _, id := range ids {
|
||||
w.identitySet[id] = true
|
||||
}
|
||||
}
|
||||
|
||||
func (w *whatChanged) Clear() {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
w.clear()
|
||||
}
|
||||
|
||||
func (w *whatChanged) clear() {
|
||||
w.identitySet = identity.Identities{}
|
||||
}
|
||||
|
||||
func (w *whatChanged) Changes() []identity.Identity {
|
||||
if w == nil || w.identitySet == nil {
|
||||
return nil
|
||||
@@ -408,14 +392,6 @@ func (w *whatChanged) Changes() []identity.Identity {
|
||||
return w.identitySet.AsSlice()
|
||||
}
|
||||
|
||||
func (w *whatChanged) Drain() []identity.Identity {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
ids := w.identitySet.AsSlice()
|
||||
w.clear()
|
||||
return ids
|
||||
}
|
||||
|
||||
// RegisterMediaTypes will register the Site's media types in the mime
|
||||
// package, so it will behave correctly with Hugo's built-in server.
|
||||
func (s *Site) RegisterMediaTypes() {
|
||||
@@ -810,7 +786,6 @@ func (s *Site) errorCollator(results <-chan error, errs chan<- error) {
|
||||
// as possible for existing sites. Most sites will use {{ .Site.GetPage "section" "my/section" }},
|
||||
// i.e. 2 arguments, so we test for that.
|
||||
func (s *Site) GetPage(ref ...string) (page.Page, error) {
|
||||
s.checkReady()
|
||||
p, err := s.s.getPageForRefs(ref...)
|
||||
|
||||
if p == nil {
|
||||
|
||||
+3
-25
@@ -32,7 +32,6 @@ import (
|
||||
"github.com/gohugoio/hugo/config/allconfig"
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
"github.com/gohugoio/hugo/hugolib/doctree"
|
||||
"github.com/gohugoio/hugo/hugolib/pagesfromdata"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
"github.com/gohugoio/hugo/langs/i18n"
|
||||
@@ -52,15 +51,7 @@ import (
|
||||
|
||||
var _ page.Site = (*Site)(nil)
|
||||
|
||||
type siteState int
|
||||
|
||||
const (
|
||||
siteStateInit siteState = iota
|
||||
siteStateReady
|
||||
)
|
||||
|
||||
type Site struct {
|
||||
state siteState
|
||||
conf *allconfig.Config
|
||||
language *langs.Language
|
||||
languagei int
|
||||
@@ -132,14 +123,14 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
|
||||
HandlerPost: logHookLast,
|
||||
Stdout: cfg.LogOut,
|
||||
Stderr: cfg.LogOut,
|
||||
StoreErrors: conf.Watching(),
|
||||
StoreErrors: conf.Running(),
|
||||
SuppressStatements: conf.IgnoredLogs(),
|
||||
}
|
||||
logger = loggers.New(logOpts)
|
||||
|
||||
}
|
||||
|
||||
memCache := dynacache.New(dynacache.Options{Watching: conf.Watching(), Log: logger})
|
||||
memCache := dynacache.New(dynacache.Options{Running: conf.Running(), Log: logger})
|
||||
|
||||
firstSiteDeps := &deps.Deps{
|
||||
Fs: cfg.Fs,
|
||||
@@ -175,8 +166,7 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
|
||||
treeResources: doctree.New(
|
||||
treeConfig,
|
||||
),
|
||||
treeTaxonomyEntries: doctree.NewTreeShiftTree[*weightedContentNode](doctree.DimensionLanguage.Index(), len(confm.Languages)),
|
||||
treePagesFromTemplateAdapters: doctree.NewTreeShiftTree[*pagesfromdata.PagesFromTemplate](doctree.DimensionLanguage.Index(), len(confm.Languages)),
|
||||
treeTaxonomyEntries: doctree.NewTreeShiftTree[*weightedContentNode](doctree.DimensionLanguage.Index(), len(confm.Languages)),
|
||||
}
|
||||
|
||||
pageTrees.createMutableTrees()
|
||||
@@ -425,7 +415,6 @@ func (s *Site) Current() page.Site {
|
||||
|
||||
// MainSections returns the list of main sections.
|
||||
func (s *Site) MainSections() []string {
|
||||
s.checkReady()
|
||||
return s.conf.C.MainSections
|
||||
}
|
||||
|
||||
@@ -444,7 +433,6 @@ func (s *Site) BaseURL() string {
|
||||
|
||||
// Deprecated: Use .Site.Lastmod instead.
|
||||
func (s *Site) LastChange() time.Time {
|
||||
s.checkReady()
|
||||
hugo.Deprecate(".Site.LastChange", "Use .Site.Lastmod instead.", "v0.123.0")
|
||||
return s.lastmod
|
||||
}
|
||||
@@ -533,7 +521,6 @@ func (s *Site) ForEeachIdentityByName(name string, f func(identity.Identity) boo
|
||||
// Pages returns all pages.
|
||||
// This is for the current language only.
|
||||
func (s *Site) Pages() page.Pages {
|
||||
s.checkReady()
|
||||
return s.pageMap.getPagesInSection(
|
||||
pageMapQueryPagesInSection{
|
||||
pageMapQueryPagesBelowPath: pageMapQueryPagesBelowPath{
|
||||
@@ -550,7 +537,6 @@ func (s *Site) Pages() page.Pages {
|
||||
// RegularPages returns all the regular pages.
|
||||
// This is for the current language only.
|
||||
func (s *Site) RegularPages() page.Pages {
|
||||
s.checkReady()
|
||||
return s.pageMap.getPagesInSection(
|
||||
pageMapQueryPagesInSection{
|
||||
pageMapQueryPagesBelowPath: pageMapQueryPagesBelowPath{
|
||||
@@ -565,18 +551,10 @@ func (s *Site) RegularPages() page.Pages {
|
||||
|
||||
// AllPages returns all pages for all sites.
|
||||
func (s *Site) AllPages() page.Pages {
|
||||
s.checkReady()
|
||||
return s.h.Pages()
|
||||
}
|
||||
|
||||
// AllRegularPages returns all regular pages for all sites.
|
||||
func (s *Site) AllRegularPages() page.Pages {
|
||||
s.checkReady()
|
||||
return s.h.RegularPages()
|
||||
}
|
||||
|
||||
func (s *Site) checkReady() {
|
||||
if s.state != siteStateReady {
|
||||
panic("this method cannot be called before the site is fully initialized")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,10 @@ import (
|
||||
|
||||
// Sections returns the top level sections.
|
||||
func (s *Site) Sections() page.Pages {
|
||||
s.checkReady()
|
||||
return s.Home().Sections()
|
||||
}
|
||||
|
||||
// Home is a shortcut to the home page, equivalent to .Site.GetPage "home".
|
||||
func (s *Site) Home() page.Page {
|
||||
s.checkReady()
|
||||
return s.s.home
|
||||
}
|
||||
|
||||
+2
-6
@@ -1,11 +1,7 @@
|
||||
# Release env.
|
||||
# These will be replaced by script before release.
|
||||
HUGORELEASER_TAG=v0.125.7
|
||||
HUGORELEASER_COMMITISH=b1d808bc373f53ad37c8966bb02a6aea095db5f8
|
||||
|
||||
|
||||
|
||||
|
||||
HUGORELEASER_TAG=v0.125.1
|
||||
HUGORELEASER_COMMITISH=68c5ad638c2072969e47262926b912e80fd71a77
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -61,7 +61,6 @@ func (tp *TranslationProvider) NewResource(dst *deps.Deps) error {
|
||||
hugofs.WalkwayConfig{
|
||||
Fs: dst.BaseFs.I18n.Fs,
|
||||
IgnoreFile: dst.SourceSpec.IgnoreFile,
|
||||
PathParser: dst.SourceSpec.Cfg.PathParser(),
|
||||
WalkFn: func(path string, info hugofs.FileMetaInfo) error {
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
Hugo adds a specific prefix, "__hugo_navigate", to the path in certain situations to signal
|
||||
navigation to another content page.
|
||||
*/
|
||||
function HugoReload() {}
|
||||
|
||||
HugoReload.identifier = 'hugoReloader';
|
||||
HugoReload.version = '0.9';
|
||||
|
||||
HugoReload.prototype.reload = function (path, options) {
|
||||
var prefix = '__hugo_navigate';
|
||||
|
||||
if (path.lastIndexOf(prefix, 0) !== 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
path = path.substring(prefix.length);
|
||||
|
||||
var portChanged = options.overrideURL && options.overrideURL != window.location.port;
|
||||
|
||||
if (!portChanged && window.location.pathname === path) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
if (portChanged) {
|
||||
window.location = location.protocol + '//' + location.hostname + ':' + options.overrideURL + path;
|
||||
} else {
|
||||
window.location.pathname = path;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
LiveReload.addPlugin(HugoReload);
|
||||
@@ -1,61 +0,0 @@
|
||||
//go:generate go run main.go
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/evanw/esbuild/pkg/api"
|
||||
)
|
||||
|
||||
//go:embed livereload-hugo-plugin.js
|
||||
var livereloadHugoPluginJS string
|
||||
|
||||
func main() {
|
||||
// 4.0.2
|
||||
// To upgrade to a new version, change to the commit hash of the version you want to upgrade to
|
||||
// then run mage generate from the root.
|
||||
const liveReloadCommit = "d803a41804d2d71e0814c4e9e3233e78991024d9"
|
||||
liveReloadSourceURL := fmt.Sprintf("https://raw.githubusercontent.com/livereload/livereload-js/%s/dist/livereload.js", liveReloadCommit)
|
||||
|
||||
func() {
|
||||
resp, err := http.Get(liveReloadSourceURL)
|
||||
must(err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
must(err)
|
||||
|
||||
// Write the unminified livereload.js file.
|
||||
err = os.WriteFile("../livereload.js", b, 0o644)
|
||||
must(err)
|
||||
|
||||
// Bundle and minify with ESBuild.
|
||||
result := api.Build(api.BuildOptions{
|
||||
Stdin: &api.StdinOptions{
|
||||
Contents: string(b) + livereloadHugoPluginJS,
|
||||
},
|
||||
Outfile: "../livereload.min.js",
|
||||
Bundle: true,
|
||||
Target: api.ES2015,
|
||||
Write: true,
|
||||
MinifyWhitespace: true,
|
||||
MinifyIdentifiers: true,
|
||||
MinifySyntax: true,
|
||||
})
|
||||
|
||||
if len(result.Errors) > 0 {
|
||||
log.Fatal(result.Errors)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func must(err error) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2015 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.
|
||||
@@ -50,7 +50,6 @@ import (
|
||||
)
|
||||
|
||||
// Prefix to signal to LiveReload that we need to navigate to another path.
|
||||
// Do not change this.
|
||||
const hugoNavigatePrefix = "__hugo_navigate"
|
||||
|
||||
var upgrader = &websocket.Upgrader{
|
||||
@@ -145,8 +144,48 @@ func ServeJS(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func liveReloadJS() []byte {
|
||||
return []byte(livereloadJS)
|
||||
return []byte(livereloadJS + hugoLiveReloadPlugin)
|
||||
}
|
||||
|
||||
//go:embed livereload.min.js
|
||||
var livereloadJS string
|
||||
var (
|
||||
// This is a patched version, see https://github.com/livereload/livereload-js/pull/84
|
||||
//go:embed livereload.js
|
||||
livereloadJS string
|
||||
hugoLiveReloadPlugin = fmt.Sprintf(`
|
||||
/*
|
||||
Hugo adds a specific prefix, "__hugo_navigate", to the path in certain situations to signal
|
||||
navigation to another content page.
|
||||
*/
|
||||
|
||||
function HugoReload() {}
|
||||
|
||||
HugoReload.identifier = 'hugoReloader';
|
||||
HugoReload.version = '0.9';
|
||||
|
||||
HugoReload.prototype.reload = function(path, options) {
|
||||
var prefix = %q;
|
||||
|
||||
if (path.lastIndexOf(prefix, 0) !== 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
path = path.substring(prefix.length);
|
||||
|
||||
var portChanged = options.overrideURL && options.overrideURL != window.location.port
|
||||
|
||||
if (!portChanged && window.location.pathname === path) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
if (portChanged) {
|
||||
window.location = location.protocol + "//" + location.hostname + ":" + options.overrideURL + path;
|
||||
} else {
|
||||
window.location.pathname = path;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
LiveReload.addPlugin(HugoReload)
|
||||
`, hugoNavigatePrefix)
|
||||
)
|
||||
|
||||
+1
-3795
File diff suppressed because one or more lines are too long
Vendored
-7
File diff suppressed because one or more lines are too long
+5
-4
@@ -79,7 +79,8 @@ func flagEnv() map[string]string {
|
||||
// Generate autogen packages
|
||||
func Generate() error {
|
||||
generatorPackages := []string{
|
||||
"livereload/gen",
|
||||
//"tpl/tplimpl/embedded/generate",
|
||||
//"resources/page/generate",
|
||||
}
|
||||
|
||||
for _, pkg := range generatorPackages {
|
||||
@@ -167,19 +168,19 @@ func testGoFlags() string {
|
||||
// Note that we don't run with the extended tag. Currently not supported in 32 bit.
|
||||
func Test386() error {
|
||||
env := map[string]string{"GOARCH": "386", "GOFLAGS": testGoFlags()}
|
||||
return runCmd(env, goexe, "test", "-p", "2", "./...")
|
||||
return runCmd(env, goexe, "test", "./...")
|
||||
}
|
||||
|
||||
// Run tests
|
||||
func Test() error {
|
||||
env := map[string]string{"GOFLAGS": testGoFlags()}
|
||||
return runCmd(env, goexe, "test", "-p", "2", "./...", "-tags", buildTags())
|
||||
return runCmd(env, goexe, "test", "./...", "-tags", buildTags())
|
||||
}
|
||||
|
||||
// Run tests with race detector
|
||||
func TestRace() error {
|
||||
env := map[string]string{"GOFLAGS": testGoFlags()}
|
||||
return runCmd(env, goexe, "test", "-p", "2", "-race", "./...", "-tags", buildTags())
|
||||
return runCmd(env, goexe, "test", "-race", "./...", "-tags", buildTags())
|
||||
}
|
||||
|
||||
// Run gofmt linter
|
||||
|
||||
@@ -17,7 +17,6 @@ package goldmark
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/gohugoio/hugo-goldmark-extensions/extras"
|
||||
"github.com/gohugoio/hugo-goldmark-extensions/passthrough"
|
||||
"github.com/gohugoio/hugo/markup/goldmark/hugocontext"
|
||||
"github.com/yuin/goldmark/util"
|
||||
@@ -114,15 +113,6 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
|
||||
|
||||
extensions = append(extensions, images.New(cfg.Parser.WrapStandAloneImageWithinParagraph))
|
||||
|
||||
extensions = append(extensions, extras.New(
|
||||
extras.Config{
|
||||
Insert: extras.InsertConfig{Enable: cfg.Extensions.Extras.Insert.Enable},
|
||||
Mark: extras.MarkConfig{Enable: cfg.Extensions.Extras.Mark.Enable},
|
||||
Subscript: extras.SubscriptConfig{Enable: cfg.Extensions.Extras.Subscript.Enable},
|
||||
Superscript: extras.SuperscriptConfig{Enable: cfg.Extensions.Extras.Superscript.Enable},
|
||||
},
|
||||
))
|
||||
|
||||
if mcfg.Highlight.CodeFences {
|
||||
extensions = append(extensions, codeblocks.New())
|
||||
}
|
||||
|
||||
@@ -49,20 +49,6 @@ var Default = Config{
|
||||
EastAsianLineBreaksStyle: "simple",
|
||||
EscapedSpace: false,
|
||||
},
|
||||
Extras: Extras{
|
||||
Superscript: Superscript{
|
||||
Enable: false,
|
||||
},
|
||||
Subscript: Subscript{
|
||||
Enable: false,
|
||||
},
|
||||
Insert: Insert{
|
||||
Enable: false,
|
||||
},
|
||||
Mark: Mark{
|
||||
Enable: false,
|
||||
},
|
||||
},
|
||||
Passthrough: Passthrough{
|
||||
Enable: false,
|
||||
Delimiters: DelimitersConfig{
|
||||
@@ -126,7 +112,6 @@ type Extensions struct {
|
||||
Typographer Typographer
|
||||
Footnote bool
|
||||
DefinitionList bool
|
||||
Extras Extras
|
||||
Passthrough Passthrough
|
||||
|
||||
// GitHub flavored markdown
|
||||
@@ -165,32 +150,7 @@ type Typographer struct {
|
||||
Apostrophe string
|
||||
}
|
||||
|
||||
// Extras holds extras configuration.
|
||||
// github.com/hugoio/hugo-goldmark-extensions/extras
|
||||
type Extras struct {
|
||||
Insert Insert
|
||||
Mark Mark
|
||||
Subscript Subscript
|
||||
Superscript Superscript
|
||||
}
|
||||
|
||||
type Insert struct {
|
||||
Enable bool
|
||||
}
|
||||
|
||||
type Mark struct {
|
||||
Enable bool
|
||||
}
|
||||
|
||||
type Subscript struct {
|
||||
Enable bool
|
||||
}
|
||||
|
||||
type Superscript struct {
|
||||
Enable bool
|
||||
}
|
||||
|
||||
// Passthrough holds passthrough configuration.
|
||||
// Passthrough hold passthrough configuration.
|
||||
// github.com/hugoio/hugo-goldmark-extensions/passthrough
|
||||
type Passthrough struct {
|
||||
// Whether to enable the extension
|
||||
|
||||
@@ -744,53 +744,3 @@ a^*=x-b^*
|
||||
%!%
|
||||
`)
|
||||
}
|
||||
|
||||
func TestExtrasExtension(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
|
||||
[markup.goldmark.extensions.extras.insert]
|
||||
enable = false
|
||||
[markup.goldmark.extensions.extras.mark]
|
||||
enable = false
|
||||
[markup.goldmark.extensions.extras.subscript]
|
||||
enable = false
|
||||
[markup.goldmark.extensions.extras.superscript]
|
||||
enable = false
|
||||
-- layouts/index.html --
|
||||
{{ .Content }}
|
||||
-- content/_index.md --
|
||||
---
|
||||
title: home
|
||||
---
|
||||
++insert++
|
||||
|
||||
==mark==
|
||||
|
||||
H~2~0
|
||||
|
||||
1^st^
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/index.html",
|
||||
"<p>++insert++</p>",
|
||||
"<p>==mark==</p>",
|
||||
"<p>H~2~0</p>",
|
||||
"<p>1^st^</p>",
|
||||
)
|
||||
|
||||
files = strings.ReplaceAll(files, "enable = false", "enable = true")
|
||||
|
||||
b = hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/index.html",
|
||||
"<p><ins>insert</ins></p>",
|
||||
"<p><mark>mark</mark></p>",
|
||||
"<p>H<sub>2</sub>0</p>",
|
||||
"<p>1<sup>st</sup></p>",
|
||||
)
|
||||
}
|
||||
|
||||
+6
-23
@@ -19,7 +19,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/markup/highlight"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
|
||||
"github.com/gohugoio/hugo/markup/markup_config"
|
||||
|
||||
@@ -45,7 +44,7 @@ func NewConverterProvider(cfg converter.ProviderConfig) (ConverterProvider, erro
|
||||
defaultHandler := mcfg.DefaultMarkdownHandler
|
||||
var defaultFound bool
|
||||
|
||||
add := func(p converter.ProviderProvider, subType string, aliases ...string) error {
|
||||
add := func(p converter.ProviderProvider, aliases ...string) error {
|
||||
c, err := p.New(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -54,7 +53,6 @@ func NewConverterProvider(cfg converter.ProviderConfig) (ConverterProvider, erro
|
||||
name := c.Name()
|
||||
|
||||
aliases = append(aliases, name)
|
||||
aliases = append(aliases, subType)
|
||||
|
||||
if strings.EqualFold(name, defaultHandler) {
|
||||
aliases = append(aliases, "markdown")
|
||||
@@ -65,21 +63,19 @@ func NewConverterProvider(cfg converter.ProviderConfig) (ConverterProvider, erro
|
||||
return nil
|
||||
}
|
||||
|
||||
contentTypes := cfg.Conf.ContentTypes().(media.ContentTypes)
|
||||
|
||||
if err := add(goldmark.Provider, contentTypes.Markdown.SubType, contentTypes.Markdown.Suffixes()...); err != nil {
|
||||
if err := add(goldmark.Provider); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := add(asciidocext.Provider, contentTypes.AsciiDoc.SubType, contentTypes.AsciiDoc.Suffixes()...); err != nil {
|
||||
if err := add(asciidocext.Provider, "ad", "adoc"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := add(rst.Provider, contentTypes.ReStructuredText.SubType, contentTypes.ReStructuredText.Suffixes()...); err != nil {
|
||||
if err := add(rst.Provider); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := add(pandoc.Provider, contentTypes.Pandoc.SubType, contentTypes.Pandoc.Suffixes()...); err != nil {
|
||||
if err := add(pandoc.Provider, "pdc"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := add(org.Provider, contentTypes.EmacsOrgMode.SubType, contentTypes.EmacsOrgMode.Suffixes()...); err != nil {
|
||||
if err := add(org.Provider); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -137,16 +133,3 @@ func addConverter(m map[string]converter.Provider, c converter.Provider, aliases
|
||||
m[alias] = c
|
||||
}
|
||||
}
|
||||
|
||||
// ResolveMarkup returns the markup type.
|
||||
func ResolveMarkup(s string) string {
|
||||
s = strings.ToLower(s)
|
||||
switch s {
|
||||
case "goldmark":
|
||||
return media.DefaultContentTypes.Markdown.SubType
|
||||
case "asciidocext":
|
||||
return media.DefaultContentTypes.AsciiDoc.SubType
|
||||
default:
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
+13
-18
@@ -34,12 +34,8 @@ type BuiltinTypes struct {
|
||||
OpenTypeFontType Type
|
||||
|
||||
// Common document types
|
||||
PDFType Type
|
||||
MarkdownType Type
|
||||
EmacsOrgModeType Type
|
||||
AsciiDocType Type
|
||||
PandocType Type
|
||||
ReStructuredTextType Type
|
||||
PDFType Type
|
||||
MarkdownType Type
|
||||
|
||||
// Common video types
|
||||
AVIType Type
|
||||
@@ -89,12 +85,8 @@ var Builtin = BuiltinTypes{
|
||||
OpenTypeFontType: Type{Type: "font/otf"},
|
||||
|
||||
// Common document types
|
||||
PDFType: Type{Type: "application/pdf"},
|
||||
MarkdownType: Type{Type: "text/markdown"},
|
||||
AsciiDocType: Type{Type: "text/asciidoc"}, // https://github.com/asciidoctor/asciidoctor/issues/2502
|
||||
PandocType: Type{Type: "text/pandoc"},
|
||||
ReStructuredTextType: Type{Type: "text/rst"}, // https://docutils.sourceforge.io/FAQ.html#what-s-the-official-mime-type-for-restructuredtext-data
|
||||
EmacsOrgModeType: Type{Type: "text/org"},
|
||||
PDFType: Type{Type: "application/pdf"},
|
||||
MarkdownType: Type{Type: "text/markdown"},
|
||||
|
||||
// Common video types
|
||||
AVIType: Type{Type: "video/x-msvideo"},
|
||||
@@ -116,7 +108,7 @@ var defaultMediaTypesConfig = map[string]any{
|
||||
"text/x-scss": map[string]any{"suffixes": []string{"scss"}},
|
||||
"text/x-sass": map[string]any{"suffixes": []string{"sass"}},
|
||||
"text/csv": map[string]any{"suffixes": []string{"csv"}},
|
||||
"text/html": map[string]any{"suffixes": []string{"html", "htm"}},
|
||||
"text/html": map[string]any{"suffixes": []string{"html"}},
|
||||
"text/javascript": map[string]any{"suffixes": []string{"js", "jsm", "mjs"}},
|
||||
"text/typescript": map[string]any{"suffixes": []string{"ts"}},
|
||||
"text/tsx": map[string]any{"suffixes": []string{"tsx"}},
|
||||
@@ -145,11 +137,7 @@ var defaultMediaTypesConfig = map[string]any{
|
||||
|
||||
// Common document types
|
||||
"application/pdf": map[string]any{"suffixes": []string{"pdf"}},
|
||||
"text/markdown": map[string]any{"suffixes": []string{"md", "mdown", "markdown"}},
|
||||
"text/asciidoc": map[string]any{"suffixes": []string{"adoc", "asciidoc", "ad"}},
|
||||
"text/pandoc": map[string]any{"suffixes": []string{"pandoc", "pdc"}},
|
||||
"text/rst": map[string]any{"suffixes": []string{"rst"}},
|
||||
"text/org": map[string]any{"suffixes": []string{"org"}},
|
||||
"text/markdown": map[string]any{"suffixes": []string{"md", "markdown"}},
|
||||
|
||||
// Common video types
|
||||
"video/x-msvideo": map[string]any{"suffixes": []string{"avi"}},
|
||||
@@ -164,3 +152,10 @@ var defaultMediaTypesConfig = map[string]any{
|
||||
|
||||
"application/octet-stream": map[string]any{},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Apply delimiter to all.
|
||||
for _, m := range defaultMediaTypesConfig {
|
||||
m.(map[string]any)["delimiter"] = "."
|
||||
}
|
||||
}
|
||||
|
||||
+2
-120
@@ -14,14 +14,13 @@
|
||||
package media
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
@@ -32,11 +31,6 @@ import (
|
||||
var DefaultTypes Types
|
||||
|
||||
func init() {
|
||||
// Apply delimiter to all.
|
||||
for _, m := range defaultMediaTypesConfig {
|
||||
m.(map[string]any)["delimiter"] = "."
|
||||
}
|
||||
|
||||
ns, err := DecodeTypes(nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -45,122 +39,17 @@ func init() {
|
||||
|
||||
// Initialize the Builtin types with values from DefaultTypes.
|
||||
v := reflect.ValueOf(&Builtin).Elem()
|
||||
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
f := v.Field(i)
|
||||
fieldName := v.Type().Field(i).Name
|
||||
builtinType := f.Interface().(Type)
|
||||
if builtinType.Type == "" {
|
||||
panic(fmt.Errorf("builtin type %q is empty", fieldName))
|
||||
}
|
||||
defaultType, found := DefaultTypes.GetByType(builtinType.Type)
|
||||
if !found {
|
||||
panic(fmt.Errorf("missing default type for field builtin type: %q", fieldName))
|
||||
panic(errors.New("missing default type for builtin type: " + builtinType.Type))
|
||||
}
|
||||
f.Set(reflect.ValueOf(defaultType))
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
DefaultContentTypes = ContentTypes{
|
||||
HTML: Builtin.HTMLType,
|
||||
Markdown: Builtin.MarkdownType,
|
||||
AsciiDoc: Builtin.AsciiDocType,
|
||||
Pandoc: Builtin.PandocType,
|
||||
ReStructuredText: Builtin.ReStructuredTextType,
|
||||
EmacsOrgMode: Builtin.EmacsOrgModeType,
|
||||
}
|
||||
|
||||
DefaultContentTypes.init()
|
||||
}
|
||||
|
||||
var DefaultContentTypes ContentTypes
|
||||
|
||||
// ContentTypes holds the media types that are considered content in Hugo.
|
||||
type ContentTypes struct {
|
||||
HTML Type
|
||||
Markdown Type
|
||||
AsciiDoc Type
|
||||
Pandoc Type
|
||||
ReStructuredText Type
|
||||
EmacsOrgMode Type
|
||||
|
||||
// Created in init().
|
||||
types Types
|
||||
extensionSet map[string]bool
|
||||
}
|
||||
|
||||
func (t *ContentTypes) init() {
|
||||
t.types = Types{t.HTML, t.Markdown, t.AsciiDoc, t.Pandoc, t.ReStructuredText, t.EmacsOrgMode}
|
||||
t.extensionSet = make(map[string]bool)
|
||||
for _, mt := range t.types {
|
||||
for _, suffix := range mt.Suffixes() {
|
||||
t.extensionSet[suffix] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t ContentTypes) IsContentSuffix(suffix string) bool {
|
||||
return t.extensionSet[suffix]
|
||||
}
|
||||
|
||||
// IsContentFile returns whether the given filename is a content file.
|
||||
func (t ContentTypes) IsContentFile(filename string) bool {
|
||||
return t.IsContentSuffix(strings.TrimPrefix(filepath.Ext(filename), "."))
|
||||
}
|
||||
|
||||
// IsIndexContentFile returns whether the given filename is an index content file.
|
||||
func (t ContentTypes) IsIndexContentFile(filename string) bool {
|
||||
if !t.IsContentFile(filename) {
|
||||
return false
|
||||
}
|
||||
|
||||
base := filepath.Base(filename)
|
||||
|
||||
return strings.HasPrefix(base, "index.") || strings.HasPrefix(base, "_index.")
|
||||
}
|
||||
|
||||
// IsHTMLSuffix returns whether the given suffix is a HTML media type.
|
||||
func (t ContentTypes) IsHTMLSuffix(suffix string) bool {
|
||||
for _, s := range t.HTML.Suffixes() {
|
||||
if s == suffix {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Types is a slice of media types.
|
||||
func (t ContentTypes) Types() Types {
|
||||
return t.types
|
||||
}
|
||||
|
||||
// FromTypes creates a new ContentTypes updated with the values from the given Types.
|
||||
func (t ContentTypes) FromTypes(types Types) ContentTypes {
|
||||
if tt, ok := types.GetByType(t.HTML.Type); ok {
|
||||
t.HTML = tt
|
||||
}
|
||||
if tt, ok := types.GetByType(t.Markdown.Type); ok {
|
||||
t.Markdown = tt
|
||||
}
|
||||
if tt, ok := types.GetByType(t.AsciiDoc.Type); ok {
|
||||
t.AsciiDoc = tt
|
||||
}
|
||||
if tt, ok := types.GetByType(t.Pandoc.Type); ok {
|
||||
t.Pandoc = tt
|
||||
}
|
||||
if tt, ok := types.GetByType(t.ReStructuredText.Type); ok {
|
||||
t.ReStructuredText = tt
|
||||
}
|
||||
if tt, ok := types.GetByType(t.EmacsOrgMode.Type); ok {
|
||||
t.EmacsOrgMode = tt
|
||||
}
|
||||
|
||||
t.init()
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// Hold the configuration for a given media type.
|
||||
type MediaTypeConfig struct {
|
||||
// The file suffixes used for this media type.
|
||||
@@ -216,10 +105,3 @@ func DecodeTypes(in map[string]any) (*config.ConfigNamespace[map[string]MediaTyp
|
||||
}
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
// TODO(bep) get rid of this.
|
||||
var DefaultPathParser = &paths.PathParser{
|
||||
IsContentExt: func(ext string) bool {
|
||||
return DefaultContentTypes.IsContentSuffix(ext)
|
||||
},
|
||||
}
|
||||
|
||||
+7
-12
@@ -114,7 +114,7 @@ func TestDefaultTypes(t *testing.T) {
|
||||
tp Type
|
||||
expectedMainType string
|
||||
expectedSubType string
|
||||
expectedSuffixes string
|
||||
expectedSuffix string
|
||||
expectedType string
|
||||
expectedString string
|
||||
}{
|
||||
@@ -122,34 +122,29 @@ func TestDefaultTypes(t *testing.T) {
|
||||
{Builtin.CSSType, "text", "css", "css", "text/css", "text/css"},
|
||||
{Builtin.SCSSType, "text", "x-scss", "scss", "text/x-scss", "text/x-scss"},
|
||||
{Builtin.CSVType, "text", "csv", "csv", "text/csv", "text/csv"},
|
||||
{Builtin.HTMLType, "text", "html", "html,htm", "text/html", "text/html"},
|
||||
{Builtin.MarkdownType, "text", "markdown", "md,mdown,markdown", "text/markdown", "text/markdown"},
|
||||
{Builtin.EmacsOrgModeType, "text", "org", "org", "text/org", "text/org"},
|
||||
{Builtin.PandocType, "text", "pandoc", "pandoc,pdc", "text/pandoc", "text/pandoc"},
|
||||
{Builtin.ReStructuredTextType, "text", "rst", "rst", "text/rst", "text/rst"},
|
||||
{Builtin.AsciiDocType, "text", "asciidoc", "adoc,asciidoc,ad", "text/asciidoc", "text/asciidoc"},
|
||||
{Builtin.JavascriptType, "text", "javascript", "js,jsm,mjs", "text/javascript", "text/javascript"},
|
||||
{Builtin.HTMLType, "text", "html", "html", "text/html", "text/html"},
|
||||
{Builtin.JavascriptType, "text", "javascript", "js", "text/javascript", "text/javascript"},
|
||||
{Builtin.TypeScriptType, "text", "typescript", "ts", "text/typescript", "text/typescript"},
|
||||
{Builtin.TSXType, "text", "tsx", "tsx", "text/tsx", "text/tsx"},
|
||||
{Builtin.JSXType, "text", "jsx", "jsx", "text/jsx", "text/jsx"},
|
||||
{Builtin.JSONType, "application", "json", "json", "application/json", "application/json"},
|
||||
{Builtin.RSSType, "application", "rss", "xml,rss", "application/rss+xml", "application/rss+xml"},
|
||||
{Builtin.RSSType, "application", "rss", "xml", "application/rss+xml", "application/rss+xml"},
|
||||
{Builtin.SVGType, "image", "svg", "svg", "image/svg+xml", "image/svg+xml"},
|
||||
{Builtin.TextType, "text", "plain", "txt", "text/plain", "text/plain"},
|
||||
{Builtin.XMLType, "application", "xml", "xml", "application/xml", "application/xml"},
|
||||
{Builtin.TOMLType, "application", "toml", "toml", "application/toml", "application/toml"},
|
||||
{Builtin.YAMLType, "application", "yaml", "yaml,yml", "application/yaml", "application/yaml"},
|
||||
{Builtin.YAMLType, "application", "yaml", "yaml", "application/yaml", "application/yaml"},
|
||||
{Builtin.PDFType, "application", "pdf", "pdf", "application/pdf", "application/pdf"},
|
||||
{Builtin.TrueTypeFontType, "font", "ttf", "ttf", "font/ttf", "font/ttf"},
|
||||
{Builtin.OpenTypeFontType, "font", "otf", "otf", "font/otf", "font/otf"},
|
||||
} {
|
||||
c.Assert(test.tp.MainType, qt.Equals, test.expectedMainType)
|
||||
c.Assert(test.tp.SubType, qt.Equals, test.expectedSubType)
|
||||
c.Assert(test.tp.SuffixesCSV, qt.Equals, test.expectedSuffixes)
|
||||
|
||||
c.Assert(test.tp.Type, qt.Equals, test.expectedType)
|
||||
c.Assert(test.tp.String(), qt.Equals, test.expectedString)
|
||||
|
||||
}
|
||||
|
||||
c.Assert(len(DefaultTypes), qt.Equals, 40)
|
||||
c.Assert(len(DefaultTypes), qt.Equals, 36)
|
||||
}
|
||||
|
||||
+3
-52
@@ -117,16 +117,13 @@ func FromContent(types Types, extensionHints []string, content []byte) Type {
|
||||
return m
|
||||
}
|
||||
|
||||
// FromStringAndExt creates a Type from a MIME string and a given extensions
|
||||
func FromStringAndExt(t string, ext ...string) (Type, error) {
|
||||
// FromStringAndExt creates a Type from a MIME string and a given extension.
|
||||
func FromStringAndExt(t, ext string) (Type, error) {
|
||||
tp, err := FromString(t)
|
||||
if err != nil {
|
||||
return tp, err
|
||||
}
|
||||
for i, e := range ext {
|
||||
ext[i] = strings.TrimPrefix(e, ".")
|
||||
}
|
||||
tp.SuffixesCSV = strings.Join(ext, ",")
|
||||
tp.SuffixesCSV = strings.TrimPrefix(ext, ".")
|
||||
tp.Delimiter = DefaultDelimiter
|
||||
tp.init()
|
||||
return tp, nil
|
||||
@@ -190,16 +187,6 @@ func (m Type) IsText() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// For internal use.
|
||||
func (m Type) IsHTML() bool {
|
||||
return m.SubType == Builtin.HTMLType.SubType
|
||||
}
|
||||
|
||||
// For internal use.
|
||||
func (m Type) IsMarkdown() bool {
|
||||
return m.SubType == Builtin.MarkdownType.SubType
|
||||
}
|
||||
|
||||
func InitMediaType(m *Type) {
|
||||
m.init()
|
||||
}
|
||||
@@ -234,26 +221,6 @@ func (t Types) Len() int { return len(t) }
|
||||
func (t Types) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||
func (t Types) Less(i, j int) bool { return t[i].Type < t[j].Type }
|
||||
|
||||
// GetBestMatch returns the best match for the given media type string.
|
||||
func (t Types) GetBestMatch(s string) (Type, bool) {
|
||||
// First try an exact match.
|
||||
if mt, found := t.GetByType(s); found {
|
||||
return mt, true
|
||||
}
|
||||
|
||||
// Try main type.
|
||||
if mt, found := t.GetBySubType(s); found {
|
||||
return mt, true
|
||||
}
|
||||
|
||||
// Try extension.
|
||||
if mt, _, found := t.GetFirstBySuffix(s); found {
|
||||
return mt, true
|
||||
}
|
||||
|
||||
return Type{}, false
|
||||
}
|
||||
|
||||
// GetByType returns a media type for tp.
|
||||
func (t Types) GetByType(tp string) (Type, bool) {
|
||||
for _, tt := range t {
|
||||
@@ -357,22 +324,6 @@ func (t Types) GetByMainSubType(mainType, subType string) (tp Type, found bool)
|
||||
return
|
||||
}
|
||||
|
||||
// GetBySubType gets a media type given a sub type e.g. "plain".
|
||||
func (t Types) GetBySubType(subType string) (tp Type, found bool) {
|
||||
for _, tt := range t {
|
||||
if strings.EqualFold(subType, tt.SubType) {
|
||||
if found {
|
||||
// ambiguous
|
||||
found = false
|
||||
return
|
||||
}
|
||||
tp = tt
|
||||
found = true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// IsZero reports whether this Type represents a zero value.
|
||||
// For internal use.
|
||||
func (m Type) IsZero() bool {
|
||||
|
||||
+2
-10
@@ -115,10 +115,10 @@ func TestFromTypeString(t *testing.T) {
|
||||
|
||||
func TestFromStringAndExt(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
f, err := FromStringAndExt("text/html", "html", "htm")
|
||||
f, err := FromStringAndExt("text/html", "html")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(f, qt.Equals, Builtin.HTMLType)
|
||||
f, err = FromStringAndExt("text/html", ".html", ".htm")
|
||||
f, err = FromStringAndExt("text/html", ".html")
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(f, qt.Equals, Builtin.HTMLType)
|
||||
}
|
||||
@@ -214,11 +214,3 @@ func BenchmarkTypeOps(b *testing.B) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsContentFile(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(DefaultContentTypes.IsContentFile(filepath.FromSlash("my/file.md")), qt.Equals, true)
|
||||
c.Assert(DefaultContentTypes.IsContentFile(filepath.FromSlash("my/file.ad")), qt.Equals, true)
|
||||
c.Assert(DefaultContentTypes.IsContentFile(filepath.FromSlash("textfile.txt")), qt.Equals, false)
|
||||
}
|
||||
|
||||
+9
-8
@@ -168,21 +168,22 @@ func (s *Store) WriteMetrics(w io.Writer) {
|
||||
s.mu.Unlock()
|
||||
|
||||
if s.calculateHints {
|
||||
fmt.Fprintf(w, " %15s %12s %12s %9s %7s %6s %5s %s\n", "cumulative", "average", "maximum", "cache", "percent", "cached", "total", "")
|
||||
fmt.Fprintf(w, " %15s %12s %12s %9s %7s %6s %5s %s\n", "duration", "duration", "duration", "potential", "cached", "count", "count", "template")
|
||||
fmt.Fprintf(w, " %15s %12s %12s %9s %7s %6s %5s %s\n", "----------", "--------", "--------", "---------", "-------", "------", "-----", "--------")
|
||||
fmt.Fprintf(w, " %13s %12s %12s %9s %7s %6s %5s %s\n", "cumulative", "average", "maximum", "cache", "percent", "cached", "total", "")
|
||||
fmt.Fprintf(w, " %13s %12s %12s %9s %7s %6s %5s %s\n", "duration", "duration", "duration", "potential", "cached", "count", "count", "template")
|
||||
fmt.Fprintf(w, " %13s %12s %12s %9s %7s %6s %5s %s\n", "----------", "--------", "--------", "---------", "-------", "------", "-----", "--------")
|
||||
} else {
|
||||
fmt.Fprintf(w, " %15s %12s %12s %5s %s\n", "cumulative", "average", "maximum", "", "")
|
||||
fmt.Fprintf(w, " %15s %12s %12s %5s %s\n", "duration", "duration", "duration", "count", "template")
|
||||
fmt.Fprintf(w, " %15s %12s %12s %5s %s\n", "----------", "--------", "--------", "-----", "--------")
|
||||
fmt.Fprintf(w, " %13s %12s %12s %5s %s\n", "cumulative", "average", "maximum", "", "")
|
||||
fmt.Fprintf(w, " %13s %12s %12s %5s %s\n", "duration", "duration", "duration", "count", "template")
|
||||
fmt.Fprintf(w, " %13s %12s %12s %5s %s\n", "----------", "--------", "--------", "-----", "--------")
|
||||
|
||||
}
|
||||
|
||||
sort.Sort(bySum(results))
|
||||
for _, v := range results {
|
||||
if s.calculateHints {
|
||||
fmt.Fprintf(w, " %15s %12s %12s %9d %7.f %6d %5d %s\n", v.sum, v.avg, v.max, v.cacheFactor, float64(v.cacheCount)/float64(v.count)*100, v.cacheCount, v.count, v.key)
|
||||
fmt.Fprintf(w, " %13s %12s %12s %9d %7.f %6d %5d %s\n", v.sum, v.avg, v.max, v.cacheFactor, float64(v.cacheCount)/float64(v.count)*100, v.cacheCount, v.count, v.key)
|
||||
} else {
|
||||
fmt.Fprintf(w, " %15s %12s %12s %5d %s\n", v.sum, v.avg, v.max, v.count, v.key)
|
||||
fmt.Fprintf(w, " %13s %12s %12s %5d %s\n", v.sum, v.avg, v.max, v.count, v.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-3
@@ -41,11 +41,20 @@ type MenuQueryProvider interface {
|
||||
IsMenuCurrent(menuID string, inme *MenuEntry) bool
|
||||
}
|
||||
|
||||
func PageMenusFromPage(ms any, p Page) (PageMenus, error) {
|
||||
if ms == nil {
|
||||
func PageMenusFromPage(p Page) (PageMenus, error) {
|
||||
params := p.Params()
|
||||
|
||||
ms, ok := params["menus"]
|
||||
if !ok {
|
||||
ms, ok = params["menu"]
|
||||
}
|
||||
|
||||
pm := PageMenus{}
|
||||
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
pm := PageMenus{}
|
||||
|
||||
me := MenuEntry{}
|
||||
SetPageValues(&me, p)
|
||||
|
||||
|
||||
@@ -104,6 +104,7 @@ func InterfaceToFrontMatter(in any, format metadecoders.Format, w io.Writer) err
|
||||
}
|
||||
|
||||
err = InterfaceToConfig(in, format, w)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -62,19 +62,15 @@ func (l *pageLexer) Input() []byte {
|
||||
return l.input
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
NoFrontMatter bool
|
||||
NoSummaryDivider bool
|
||||
}
|
||||
type Config struct{}
|
||||
|
||||
// note: the input position here is normally 0 (start), but
|
||||
// can be set if position of first shortcode is known
|
||||
func newPageLexer(input []byte, stateStart stateFunc, cfg Config) *pageLexer {
|
||||
lexer := &pageLexer{
|
||||
input: input,
|
||||
stateStart: stateStart,
|
||||
summaryDivider: summaryDivider,
|
||||
cfg: cfg,
|
||||
input: input,
|
||||
stateStart: stateStart,
|
||||
cfg: cfg,
|
||||
lexerShortcodeState: lexerShortcodeState{
|
||||
currLeftDelimItem: tLeftDelimScNoMarkup,
|
||||
currRightDelimItem: tRightDelimScNoMarkup,
|
||||
@@ -299,8 +295,6 @@ func (s *sectionHandlers) skip() int {
|
||||
}
|
||||
|
||||
func createSectionHandlers(l *pageLexer) *sectionHandlers {
|
||||
handlers := make([]*sectionHandler, 0, 2)
|
||||
|
||||
shortCodeHandler := §ionHandler{
|
||||
l: l,
|
||||
skipFunc: func(l *pageLexer) int {
|
||||
@@ -336,36 +330,31 @@ func createSectionHandlers(l *pageLexer) *sectionHandlers {
|
||||
},
|
||||
}
|
||||
|
||||
handlers = append(handlers, shortCodeHandler)
|
||||
summaryDividerHandler := §ionHandler{
|
||||
l: l,
|
||||
skipFunc: func(l *pageLexer) int {
|
||||
if l.summaryDividerChecked || l.summaryDivider == nil {
|
||||
return -1
|
||||
}
|
||||
return l.index(l.summaryDivider)
|
||||
},
|
||||
lexFunc: func(origin stateFunc, l *pageLexer) (stateFunc, bool) {
|
||||
if !l.hasPrefix(l.summaryDivider) {
|
||||
return origin, false
|
||||
}
|
||||
|
||||
if !l.cfg.NoSummaryDivider {
|
||||
summaryDividerHandler := §ionHandler{
|
||||
l: l,
|
||||
skipFunc: func(l *pageLexer) int {
|
||||
if l.summaryDividerChecked {
|
||||
return -1
|
||||
}
|
||||
return l.index(l.summaryDivider)
|
||||
},
|
||||
lexFunc: func(origin stateFunc, l *pageLexer) (stateFunc, bool) {
|
||||
if !l.hasPrefix(l.summaryDivider) {
|
||||
return origin, false
|
||||
}
|
||||
|
||||
l.summaryDividerChecked = true
|
||||
l.pos += len(l.summaryDivider)
|
||||
// This makes it a little easier to reason about later.
|
||||
l.consumeSpace()
|
||||
l.emit(TypeLeadSummaryDivider)
|
||||
|
||||
return origin, true
|
||||
},
|
||||
}
|
||||
|
||||
handlers = append(handlers, summaryDividerHandler)
|
||||
l.summaryDividerChecked = true
|
||||
l.pos += len(l.summaryDivider)
|
||||
// This makes it a little easier to reason about later.
|
||||
l.consumeSpace()
|
||||
l.emit(TypeLeadSummaryDivider)
|
||||
|
||||
return origin, true
|
||||
},
|
||||
}
|
||||
|
||||
handlers := []*sectionHandler{shortCodeHandler, summaryDividerHandler}
|
||||
|
||||
return §ionHandlers{
|
||||
l: l,
|
||||
handlers: handlers,
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
package pageparser
|
||||
|
||||
func lexIntroSection(l *pageLexer) stateFunc {
|
||||
l.summaryDivider = summaryDivider
|
||||
|
||||
LOOP:
|
||||
for {
|
||||
r := l.next()
|
||||
|
||||
@@ -36,11 +36,16 @@ var _ Result = (*pageLexer)(nil)
|
||||
|
||||
// ParseBytes parses the page in b according to the given Config.
|
||||
func ParseBytes(b []byte, cfg Config) (Items, error) {
|
||||
startLexer := lexIntroSection
|
||||
if cfg.NoFrontMatter {
|
||||
startLexer = lexMainSection
|
||||
l, err := parseBytes(b, cfg, lexIntroSection)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l, err := parseBytes(b, cfg, startLexer)
|
||||
return l.items, l.err
|
||||
}
|
||||
|
||||
// ParseBytesMain parses b starting with the main section.
|
||||
func ParseBytesMain(b []byte, cfg Config) (Items, error) {
|
||||
l, err := parseBytes(b, cfg, lexMainSection)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -101,14 +101,3 @@ func BenchmarkHasShortcode(b *testing.B) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSummaryDividerStartingFromMain(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
input := `aaa <!--more--> bbb`
|
||||
items, err := collectStringMain(input)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
c.Assert(items, qt.HasLen, 4)
|
||||
c.Assert(items[1].Type, qt.Equals, TypeLeadSummaryDivider)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2017-present 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,8 +92,6 @@ func (r *ReleaseHandler) Run() error {
|
||||
mainVersion := newVersion
|
||||
mainVersion.PatchLevel = 0
|
||||
|
||||
r.gitPull()
|
||||
|
||||
defer r.gitPush()
|
||||
|
||||
if r.step == 1 {
|
||||
@@ -180,12 +178,6 @@ func (r ReleaseHandler) calculateVersions() (hugo.Version, hugo.Version) {
|
||||
return newVersion, finalVersion
|
||||
}
|
||||
|
||||
func (r *ReleaseHandler) gitPull() {
|
||||
if _, err := r.git("pull", "origin", "HEAD"); err != nil {
|
||||
log.Fatal("pull failed:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ReleaseHandler) gitPush() {
|
||||
if r.skipPush {
|
||||
return
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/big"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -141,12 +140,6 @@ func (d *Decoder) Decode(r io.Reader) (ex *ExifInfo, err error) {
|
||||
|
||||
if !d.noLatLong {
|
||||
lat, long, _ = x.LatLong()
|
||||
if math.IsNaN(lat) {
|
||||
lat = 0
|
||||
}
|
||||
if math.IsNaN(long) {
|
||||
long = 0
|
||||
}
|
||||
}
|
||||
|
||||
walker := &exifWalker{x: x, vals: make(map[string]any), includeMatcher: d.includeFieldsRe, excludeMatcher: d.excludeFieldsrRe}
|
||||
|
||||
@@ -225,6 +225,9 @@ type PageMetaProvider interface {
|
||||
// to the source of this Page. It will be relative to any content root.
|
||||
Path() string
|
||||
|
||||
// This is for internal use only.
|
||||
PathInfo() *paths.Path
|
||||
|
||||
// The slug, typically defined in front matter.
|
||||
Slug() string
|
||||
|
||||
@@ -250,12 +253,6 @@ type PageMetaProvider interface {
|
||||
Weight() int
|
||||
}
|
||||
|
||||
// PageMetaInternalProvider provides internal page metadata.
|
||||
type PageMetaInternalProvider interface {
|
||||
// This is for internal use only.
|
||||
PathInfo() *paths.Path
|
||||
}
|
||||
|
||||
// PageRenderProvider provides a way for a Page to render content.
|
||||
type PageRenderProvider interface {
|
||||
// Render renders the given layout with this Page as context.
|
||||
@@ -276,7 +273,6 @@ type PageWithoutContent interface {
|
||||
RenderShortcodesProvider
|
||||
resource.Resource
|
||||
PageMetaProvider
|
||||
PageMetaInternalProvider
|
||||
resource.LanguageProvider
|
||||
|
||||
// For pages backed by a file.
|
||||
|
||||
@@ -17,8 +17,9 @@ package page
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/config"
|
||||
)
|
||||
|
||||
func MarshalPageToJSON(p Page) ([]byte, error) {
|
||||
@@ -38,6 +39,7 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
|
||||
isNode := p.IsNode()
|
||||
isPage := p.IsPage()
|
||||
path := p.Path()
|
||||
pathc := p.Path()
|
||||
slug := p.Slug()
|
||||
lang := p.Lang()
|
||||
isSection := p.IsSection()
|
||||
@@ -63,6 +65,7 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
|
||||
IsNode bool
|
||||
IsPage bool
|
||||
Path string
|
||||
Pathc string
|
||||
Slug string
|
||||
Lang string
|
||||
IsSection bool
|
||||
@@ -87,6 +90,7 @@ func MarshalPageToJSON(p Page) ([]byte, error) {
|
||||
IsNode: isNode,
|
||||
IsPage: isPage,
|
||||
Path: path,
|
||||
Pathc: pathc,
|
||||
Slug: slug,
|
||||
Lang: lang,
|
||||
IsSection: isSection,
|
||||
|
||||
@@ -57,7 +57,7 @@ var (
|
||||
// PageNop implements Page, but does nothing.
|
||||
type nopPage int
|
||||
|
||||
var noOpPathInfo = media.DefaultPathParser.Parse(files.ComponentFolderContent, "no-op.md")
|
||||
var noOpPathInfo = paths.Parse(files.ComponentFolderContent, "no-op.md")
|
||||
|
||||
func (p *nopPage) Err() resource.ResourceError {
|
||||
return nil
|
||||
|
||||
@@ -14,24 +14,14 @@
|
||||
package pagemeta
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hreflect"
|
||||
"github.com/gohugoio/hugo/common/htime"
|
||||
"github.com/gohugoio/hugo/common/hugio"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
"github.com/gohugoio/hugo/markup"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/gohugoio/hugo/resources/kinds"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
"github.com/gohugoio/hugo/resources/resource"
|
||||
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
|
||||
@@ -39,13 +29,6 @@ import (
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
type DatesStrings struct {
|
||||
Date string `json:"date"`
|
||||
Lastmod string `json:"lastMod"`
|
||||
PublishDate string `json:"publishDate"`
|
||||
ExpiryDate string `json:"expiryDate"`
|
||||
}
|
||||
|
||||
type Dates struct {
|
||||
Date time.Time
|
||||
Lastmod time.Time
|
||||
@@ -74,231 +57,40 @@ func (d Dates) IsAllDatesZero() bool {
|
||||
// Note that all the top level fields are reserved Hugo keywords.
|
||||
// Any custom configuration needs to be set in the Params map.
|
||||
type PageConfig struct {
|
||||
Dates Dates `json:"-"` // Dates holds the four core dates for this page.
|
||||
DatesStrings
|
||||
Dates // Dates holds the four core dates for this page.
|
||||
Title string // The title of the page.
|
||||
LinkTitle string // The link title of the page.
|
||||
Type string // The content type of the page.
|
||||
Layout string // The layout to use for to render this page.
|
||||
Markup string // The markup used in the content file.
|
||||
Weight int // The weight of the page, used in sorting if set to a non-zero value.
|
||||
Kind string // The kind of page, e.g. "page", "section", "home" etc. This is usually derived from the content path.
|
||||
Path string // The canonical path to the page, e.g. /sect/mypage. Note: Leading slash, no trailing slash, no extensions or language identifiers.
|
||||
Lang string // The language code for this page. This is usually derived from the module mount or filename.
|
||||
URL string // The URL to the rendered page, e.g. /sect/mypage.html.
|
||||
Lang string // The language code for this page. This is usually derived from the module mount or filename.
|
||||
Slug string // The slug for this page.
|
||||
Description string // The description for this page.
|
||||
Summary string // The summary for this page.
|
||||
Draft bool // Whether or not the content is a draft.
|
||||
Headless bool `json:"-"` // Whether or not the page should be rendered.
|
||||
Headless bool // Whether or not the page should be rendered.
|
||||
IsCJKLanguage bool // Whether or not the content is in a CJK language.
|
||||
TranslationKey string // The translation key for this page.
|
||||
Keywords []string // The keywords for this page.
|
||||
Aliases []string // The aliases for this page.
|
||||
Outputs []string // The output formats to render this page in. If not set, the site's configured output formats for this page kind will be used.
|
||||
|
||||
FrontMatterOnlyValues `mapstructure:"-" json:"-"`
|
||||
|
||||
Cascade []map[string]any
|
||||
Sitemap config.SitemapConfig
|
||||
Build BuildConfig
|
||||
Menus []string
|
||||
// These build options are set in the front matter,
|
||||
// but not passed on to .Params.
|
||||
Resources []map[string]any
|
||||
Cascade map[page.PageMatcher]maps.Params // Only relevant for branch nodes.
|
||||
Sitemap config.SitemapConfig
|
||||
Build BuildConfig
|
||||
|
||||
// User defined params.
|
||||
Params maps.Params
|
||||
|
||||
// Content holds the content for this page.
|
||||
Content Source
|
||||
|
||||
// Compiled values.
|
||||
CascadeCompiled map[page.PageMatcher]maps.Params
|
||||
ContentMediaType media.Type `mapstructure:"-" json:"-"`
|
||||
IsFromContentAdapter bool `mapstructure:"-" json:"-"`
|
||||
}
|
||||
|
||||
var DefaultPageConfig = PageConfig{
|
||||
Build: DefaultBuildConfig,
|
||||
}
|
||||
|
||||
func (p *PageConfig) Validate(pagesFromData bool) error {
|
||||
if pagesFromData {
|
||||
if p.Path == "" {
|
||||
return errors.New("path must be set")
|
||||
}
|
||||
if strings.HasPrefix(p.Path, "/") {
|
||||
return fmt.Errorf("path %q must not start with a /", p.Path)
|
||||
}
|
||||
if p.Lang != "" {
|
||||
return errors.New("lang must not be set")
|
||||
}
|
||||
|
||||
if p.Content.Markup != "" {
|
||||
return errors.New("markup must not be set, use mediaType")
|
||||
}
|
||||
}
|
||||
|
||||
if p.Cascade != nil {
|
||||
if !kinds.IsBranch(p.Kind) {
|
||||
return errors.New("cascade is only supported for branch nodes")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Compile sets up the page configuration after all fields have been set.
|
||||
func (p *PageConfig) Compile(basePath string, pagesFromData bool, ext string, logger loggers.Logger, mediaTypes media.Types) error {
|
||||
// In content adapters, we always get relative paths.
|
||||
if basePath != "" {
|
||||
p.Path = path.Join(basePath, p.Path)
|
||||
}
|
||||
|
||||
if p.Params == nil {
|
||||
p.Params = make(maps.Params)
|
||||
}
|
||||
maps.PrepareParams(p.Params)
|
||||
|
||||
if p.Content.Markup == "" && p.Content.MediaType == "" {
|
||||
if ext == "" {
|
||||
ext = "md"
|
||||
}
|
||||
p.ContentMediaType = MarkupToMediaType(ext, mediaTypes)
|
||||
if p.ContentMediaType.IsZero() {
|
||||
return fmt.Errorf("failed to resolve media type for suffix %q", ext)
|
||||
}
|
||||
}
|
||||
|
||||
var s string
|
||||
if p.ContentMediaType.IsZero() {
|
||||
if p.Content.MediaType != "" {
|
||||
s = p.Content.MediaType
|
||||
p.ContentMediaType, _ = mediaTypes.GetByType(s)
|
||||
}
|
||||
|
||||
if p.ContentMediaType.IsZero() && p.Content.Markup != "" {
|
||||
s = p.Content.Markup
|
||||
p.ContentMediaType = MarkupToMediaType(s, mediaTypes)
|
||||
}
|
||||
}
|
||||
|
||||
if p.ContentMediaType.IsZero() {
|
||||
return fmt.Errorf("failed to resolve media type for %q", s)
|
||||
}
|
||||
|
||||
if p.Content.Markup == "" {
|
||||
p.Content.Markup = p.ContentMediaType.SubType
|
||||
}
|
||||
|
||||
if pagesFromData {
|
||||
if p.Kind == "" {
|
||||
p.Kind = kinds.KindPage
|
||||
}
|
||||
|
||||
// Note that NormalizePathStringBasic will make sure that we don't preserve the unnormalized path.
|
||||
// We do that when we create pages from the file system; mostly for backward compatibility,
|
||||
// but also because people tend to use use the filename to name their resources (with spaces and all),
|
||||
// and this isn't relevant when creating resources from an API where it's easy to add textual meta data.
|
||||
p.Path = paths.NormalizePathStringBasic(p.Path)
|
||||
}
|
||||
|
||||
if p.Cascade != nil {
|
||||
cascade, err := page.DecodeCascade(logger, p.Cascade)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode cascade: %w", err)
|
||||
}
|
||||
p.CascadeCompiled = cascade
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarkupToMediaType converts a markup string to a media type.
|
||||
func MarkupToMediaType(s string, mediaTypes media.Types) media.Type {
|
||||
s = strings.ToLower(s)
|
||||
mt, _ := mediaTypes.GetBestMatch(markup.ResolveMarkup(s))
|
||||
return mt
|
||||
}
|
||||
|
||||
type ResourceConfig struct {
|
||||
Path string
|
||||
Name string
|
||||
Title string
|
||||
Params maps.Params
|
||||
Content Source
|
||||
|
||||
// Compiled values.
|
||||
PathInfo *paths.Path `mapstructure:"-" json:"-"`
|
||||
ContentMediaType media.Type
|
||||
}
|
||||
|
||||
func (rc *ResourceConfig) Validate() error {
|
||||
if rc.Path == "" {
|
||||
return errors.New("path must be set")
|
||||
}
|
||||
if rc.Content.Markup != "" {
|
||||
return errors.New("markup must not be set, use mediaType")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rc *ResourceConfig) Compile(basePath string, pathParser *paths.PathParser, mediaTypes media.Types) error {
|
||||
if rc.Params != nil {
|
||||
maps.PrepareParams(rc.Params)
|
||||
}
|
||||
|
||||
// Note that NormalizePathStringBasic will make sure that we don't preserve the unnormalized path.
|
||||
// We do that when we create resources from the file system; mostly for backward compatibility,
|
||||
// but also because people tend to use use the filename to name their resources (with spaces and all),
|
||||
// and this isn't relevant when creating resources from an API where it's easy to add textual meta data.
|
||||
rc.Path = paths.NormalizePathStringBasic(path.Join(basePath, rc.Path))
|
||||
rc.PathInfo = pathParser.Parse(files.ComponentFolderContent, rc.Path)
|
||||
if rc.Content.MediaType != "" {
|
||||
var found bool
|
||||
rc.ContentMediaType, found = mediaTypes.GetByType(rc.Content.MediaType)
|
||||
if !found {
|
||||
return fmt.Errorf("media type %q not found", rc.Content.MediaType)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Source struct {
|
||||
// MediaType is the media type of the content.
|
||||
MediaType string
|
||||
|
||||
// The markup used in Value. Only used in front matter.
|
||||
Markup string
|
||||
|
||||
// The content.
|
||||
Value any
|
||||
}
|
||||
|
||||
func (s Source) IsZero() bool {
|
||||
return !hreflect.IsTruthful(s.Value)
|
||||
}
|
||||
|
||||
func (s Source) IsResourceValue() bool {
|
||||
_, ok := s.Value.(resource.Resource)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s Source) ValueAsString() string {
|
||||
if s.Value == nil {
|
||||
return ""
|
||||
}
|
||||
ss, err := cast.ToStringE(s.Value)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("content source: failed to convert %T to string: %s", s.Value, err))
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
func (s Source) ValueAsOpenReadSeekCloser() hugio.OpenReadSeekCloser {
|
||||
return hugio.NewOpenReadSeekCloser(hugio.NewReadSeekerNoOpCloserFromString(s.ValueAsString()))
|
||||
}
|
||||
|
||||
// FrontMatterOnlyValues holds values that can only be set via front matter.
|
||||
type FrontMatterOnlyValues struct {
|
||||
ResourcesMeta []map[string]any
|
||||
IsGoldmark bool `json:"-"`
|
||||
}
|
||||
|
||||
// FrontMatterHandler maps front matter into Page fields and .Params.
|
||||
@@ -306,8 +98,6 @@ type FrontMatterOnlyValues struct {
|
||||
type FrontMatterHandler struct {
|
||||
fmConfig FrontmatterConfig
|
||||
|
||||
contentAdapterDatesHandler func(d *FrontMatterDescriptor) error
|
||||
|
||||
dateHandler frontMatterFieldHandler
|
||||
lastModHandler frontMatterFieldHandler
|
||||
publishDateHandler frontMatterFieldHandler
|
||||
@@ -354,13 +144,6 @@ func (f FrontMatterHandler) HandleDates(d *FrontMatterDescriptor) error {
|
||||
panic("missing pageConfig")
|
||||
}
|
||||
|
||||
if d.PageConfig.IsFromContentAdapter {
|
||||
if f.contentAdapterDatesHandler == nil {
|
||||
panic("missing content adapter date handler")
|
||||
}
|
||||
return f.contentAdapterDatesHandler(d)
|
||||
}
|
||||
|
||||
if f.dateHandler == nil {
|
||||
panic("missing date handler")
|
||||
}
|
||||
@@ -569,13 +352,9 @@ func NewFrontmatterHandler(logger loggers.Logger, frontMatterConfig FrontmatterC
|
||||
func (f *FrontMatterHandler) createHandlers() error {
|
||||
var err error
|
||||
|
||||
if f.contentAdapterDatesHandler, err = f.createContentAdapterDatesHandler(f.fmConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f.dateHandler, err = f.createDateHandler(f.fmConfig.Date,
|
||||
func(d *FrontMatterDescriptor, t time.Time) {
|
||||
d.PageConfig.Dates.Date = t
|
||||
d.PageConfig.Date = t
|
||||
setParamIfNotSet(fmDate, t, d)
|
||||
}); err != nil {
|
||||
return err
|
||||
@@ -584,7 +363,7 @@ func (f *FrontMatterHandler) createHandlers() error {
|
||||
if f.lastModHandler, err = f.createDateHandler(f.fmConfig.Lastmod,
|
||||
func(d *FrontMatterDescriptor, t time.Time) {
|
||||
setParamIfNotSet(fmLastmod, t, d)
|
||||
d.PageConfig.Dates.Lastmod = t
|
||||
d.PageConfig.Lastmod = t
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -592,7 +371,7 @@ func (f *FrontMatterHandler) createHandlers() error {
|
||||
if f.publishDateHandler, err = f.createDateHandler(f.fmConfig.PublishDate,
|
||||
func(d *FrontMatterDescriptor, t time.Time) {
|
||||
setParamIfNotSet(fmPubDate, t, d)
|
||||
d.PageConfig.Dates.PublishDate = t
|
||||
d.PageConfig.PublishDate = t
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -600,7 +379,7 @@ func (f *FrontMatterHandler) createHandlers() error {
|
||||
if f.expiryDateHandler, err = f.createDateHandler(f.fmConfig.ExpiryDate,
|
||||
func(d *FrontMatterDescriptor, t time.Time) {
|
||||
setParamIfNotSet(fmExpiryDate, t, d)
|
||||
d.PageConfig.Dates.ExpiryDate = t
|
||||
d.PageConfig.ExpiryDate = t
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -615,86 +394,6 @@ func setParamIfNotSet(key string, value any, d *FrontMatterDescriptor) {
|
||||
d.PageConfig.Params[key] = value
|
||||
}
|
||||
|
||||
func (f FrontMatterHandler) createContentAdapterDatesHandler(fmcfg FrontmatterConfig) (func(d *FrontMatterDescriptor) error, error) {
|
||||
setTime := func(key string, value time.Time, in *PageConfig) {
|
||||
switch key {
|
||||
case fmDate:
|
||||
in.Dates.Date = value
|
||||
case fmLastmod:
|
||||
in.Dates.Lastmod = value
|
||||
case fmPubDate:
|
||||
in.Dates.PublishDate = value
|
||||
case fmExpiryDate:
|
||||
in.Dates.ExpiryDate = value
|
||||
}
|
||||
}
|
||||
|
||||
getTime := func(key string, in *PageConfig) time.Time {
|
||||
switch key {
|
||||
case fmDate:
|
||||
return in.Dates.Date
|
||||
case fmLastmod:
|
||||
return in.Dates.Lastmod
|
||||
case fmPubDate:
|
||||
return in.Dates.PublishDate
|
||||
case fmExpiryDate:
|
||||
return in.Dates.ExpiryDate
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
createSetter := func(identifiers []string, date string) func(pcfg *PageConfig) {
|
||||
var getTimes []func(in *PageConfig) time.Time
|
||||
for _, identifier := range identifiers {
|
||||
if strings.HasPrefix(identifier, ":") {
|
||||
continue
|
||||
}
|
||||
switch identifier {
|
||||
case fmDate:
|
||||
getTimes = append(getTimes, func(in *PageConfig) time.Time {
|
||||
return getTime(fmDate, in)
|
||||
})
|
||||
case fmLastmod:
|
||||
getTimes = append(getTimes, func(in *PageConfig) time.Time {
|
||||
return getTime(fmLastmod, in)
|
||||
})
|
||||
case fmPubDate:
|
||||
getTimes = append(getTimes, func(in *PageConfig) time.Time {
|
||||
return getTime(fmPubDate, in)
|
||||
})
|
||||
case fmExpiryDate:
|
||||
getTimes = append(getTimes, func(in *PageConfig) time.Time {
|
||||
return getTime(fmExpiryDate, in)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return func(pcfg *PageConfig) {
|
||||
for _, get := range getTimes {
|
||||
if t := get(pcfg); !t.IsZero() {
|
||||
setTime(date, t, pcfg)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setDate := createSetter(fmcfg.Date, fmDate)
|
||||
setLastmod := createSetter(fmcfg.Lastmod, fmLastmod)
|
||||
setPublishDate := createSetter(fmcfg.PublishDate, fmPubDate)
|
||||
setExpiryDate := createSetter(fmcfg.ExpiryDate, fmExpiryDate)
|
||||
|
||||
fn := func(d *FrontMatterDescriptor) error {
|
||||
pcfg := d.PageConfig
|
||||
setDate(pcfg)
|
||||
setLastmod(pcfg)
|
||||
setPublishDate(pcfg)
|
||||
setExpiryDate(pcfg)
|
||||
return nil
|
||||
}
|
||||
return fn, nil
|
||||
}
|
||||
|
||||
func (f FrontMatterHandler) createDateHandler(identifiers []string, setter func(d *FrontMatterDescriptor, t time.Time)) (frontMatterFieldHandler, error) {
|
||||
var h *frontmatterFieldHandlers
|
||||
var handlers []frontMatterFieldHandler
|
||||
|
||||
@@ -18,10 +18,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/config/testconfig"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
|
||||
"github.com/gohugoio/hugo/resources/page/pagemeta"
|
||||
|
||||
@@ -150,32 +148,3 @@ func TestFrontMatterDatesDefaultKeyword(t *testing.T) {
|
||||
c.Assert(d.PageConfig.Dates.PublishDate.Day(), qt.Equals, 4)
|
||||
c.Assert(d.PageConfig.Dates.ExpiryDate.IsZero(), qt.Equals, true)
|
||||
}
|
||||
|
||||
func TestContentMediaTypeFromMarkup(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
logger := loggers.NewDefault()
|
||||
|
||||
for _, test := range []struct {
|
||||
in string
|
||||
expected string
|
||||
}{
|
||||
{"", "text/markdown"},
|
||||
{"md", "text/markdown"},
|
||||
{"markdown", "text/markdown"},
|
||||
{"mdown", "text/markdown"},
|
||||
{"goldmark", "text/markdown"},
|
||||
{"html", "text/html"},
|
||||
{"htm", "text/html"},
|
||||
{"asciidoc", "text/asciidoc"},
|
||||
{"asciidocext", "text/asciidoc"},
|
||||
{"adoc", "text/asciidoc"},
|
||||
{"pandoc", "text/pandoc"},
|
||||
{"pdc", "text/pandoc"},
|
||||
{"rst", "text/rst"},
|
||||
} {
|
||||
var pc pagemeta.PageConfig
|
||||
pc.Content.Markup = test.in
|
||||
c.Assert(pc.Compile("", true, "", logger, media.DefaultTypes), qt.IsNil)
|
||||
c.Assert(pc.ContentMediaType.Type, qt.Equals, test.expected)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ const (
|
||||
Link = "link"
|
||||
)
|
||||
|
||||
var DefaultBuildConfig = BuildConfig{
|
||||
var defaultBuildConfig = BuildConfig{
|
||||
List: Always,
|
||||
Render: Always,
|
||||
PublishResources: true,
|
||||
@@ -69,7 +69,7 @@ func (b BuildConfig) IsZero() bool {
|
||||
}
|
||||
|
||||
func DecodeBuildConfig(m any) (BuildConfig, error) {
|
||||
b := DefaultBuildConfig
|
||||
b := defaultBuildConfig
|
||||
if m == nil {
|
||||
return b, nil
|
||||
}
|
||||
|
||||
@@ -139,15 +139,8 @@ type Site interface {
|
||||
// Sites represents an ordered list of sites (languages).
|
||||
type Sites []Site
|
||||
|
||||
// Deprecated: Use .Sites.Default instead.
|
||||
// First is a convenience method to get the first Site, i.e. the main language.
|
||||
func (s Sites) First() Site {
|
||||
hugo.Deprecate(".Sites.First", "Use .Sites.Default instead.", "v0.127.0")
|
||||
return s.Default()
|
||||
}
|
||||
|
||||
// Default is a convenience method to get the site corresponding to the default
|
||||
// content language.
|
||||
func (s Sites) Default() Site {
|
||||
if len(s) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -172,7 +165,7 @@ func (s *siteWrapper) Key() string {
|
||||
return s.s.Language().Lang
|
||||
}
|
||||
|
||||
// Deprecated: Use .Site.Params instead.
|
||||
// // Deprecated: Use .Site.Params instead.
|
||||
func (s *siteWrapper) Social() map[string]string {
|
||||
return s.s.Social()
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
// 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 page_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
)
|
||||
|
||||
// Issue 12513
|
||||
func TestPageSiteSitesDefault(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
|
||||
defaultContentLanguage = 'de'
|
||||
defaultContentLanguageInSubdir = true
|
||||
[languages.en]
|
||||
languageName = 'English'
|
||||
weight = 1
|
||||
[languages.de]
|
||||
languageName = 'Deutsch'
|
||||
weight = 2
|
||||
-- layouts/index.html --
|
||||
{{ .Site.Sites.Default.Language.LanguageName }}
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/de/index.html", "Deutsch")
|
||||
}
|
||||
@@ -36,8 +36,6 @@ func TestCreatePlaceholders(t *testing.T) {
|
||||
"SuffixesCSV": "pre_foo.SuffixesCSV_post",
|
||||
"Delimiter": "pre_foo.Delimiter_post",
|
||||
"FirstSuffix": "pre_foo.FirstSuffix_post",
|
||||
"IsHTML": "pre_foo.IsHTML_post",
|
||||
"IsMarkdown": "pre_foo.IsMarkdown_post",
|
||||
"IsText": "pre_foo.IsText_post",
|
||||
"String": "pre_foo.String_post",
|
||||
"Type": "pre_foo.Type_post",
|
||||
|
||||
+8
-25
@@ -65,9 +65,6 @@ type ResourceSourceDescriptor struct {
|
||||
// The name of the resource as it was read from the source.
|
||||
NameOriginal string
|
||||
|
||||
// The title of the resource.
|
||||
Title string
|
||||
|
||||
// Any base paths prepended to the target path. This will also typically be the
|
||||
// language code, but setting it here means that it should not have any effect on
|
||||
// the permalink.
|
||||
@@ -82,9 +79,6 @@ type ResourceSourceDescriptor struct {
|
||||
// The Data to associate with this resource.
|
||||
Data map[string]any
|
||||
|
||||
// The Params to associate with this resource.
|
||||
Params maps.Params
|
||||
|
||||
// Delay publishing until either Permalink or RelPermalink is called. Maybe never.
|
||||
LazyPublish bool
|
||||
|
||||
@@ -113,12 +107,8 @@ func (fd *ResourceSourceDescriptor) init(r *Spec) error {
|
||||
panic(errors.New("RelPath is empty"))
|
||||
}
|
||||
|
||||
if fd.Params == nil {
|
||||
fd.Params = make(maps.Params)
|
||||
}
|
||||
|
||||
if fd.Path == nil {
|
||||
fd.Path = r.Cfg.PathParser().Parse("", fd.TargetPath)
|
||||
fd.Path = paths.Parse("", fd.TargetPath)
|
||||
}
|
||||
|
||||
if fd.TargetPath == "" {
|
||||
@@ -153,10 +143,6 @@ func (fd *ResourceSourceDescriptor) init(r *Spec) error {
|
||||
fd.NameOriginal = fd.NameNormalized
|
||||
}
|
||||
|
||||
if fd.Title == "" {
|
||||
fd.Title = fd.NameOriginal
|
||||
}
|
||||
|
||||
mediaType := fd.MediaType
|
||||
if mediaType.IsZero() {
|
||||
ext := fd.Path.Ext()
|
||||
@@ -310,19 +296,16 @@ type hashProvider interface {
|
||||
hash() string
|
||||
}
|
||||
|
||||
var _ resource.StaleInfo = (*StaleValue[any])(nil)
|
||||
|
||||
type StaleValue[V any] struct {
|
||||
// The value.
|
||||
Value V
|
||||
|
||||
// StaleVersionFunc reports the current version of the value.
|
||||
// This always starts out at 0 and get incremented on staleness.
|
||||
StaleVersionFunc func() uint32
|
||||
// IsStaleFunc reports whether the value is stale.
|
||||
IsStaleFunc func() bool
|
||||
}
|
||||
|
||||
func (s *StaleValue[V]) StaleVersion() uint32 {
|
||||
return s.StaleVersionFunc()
|
||||
func (s *StaleValue[V]) IsStale() bool {
|
||||
return s.IsStaleFunc()
|
||||
}
|
||||
|
||||
type AtomicStaler struct {
|
||||
@@ -330,11 +313,11 @@ type AtomicStaler struct {
|
||||
}
|
||||
|
||||
func (s *AtomicStaler) MarkStale() {
|
||||
atomic.AddUint32(&s.stale, 1)
|
||||
atomic.StoreUint32(&s.stale, 1)
|
||||
}
|
||||
|
||||
func (s *AtomicStaler) StaleVersion() uint32 {
|
||||
return atomic.LoadUint32(&(s.stale))
|
||||
func (s *AtomicStaler) IsStale() bool {
|
||||
return atomic.LoadUint32(&(s.stale)) > 0
|
||||
}
|
||||
|
||||
// For internal use.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user