diff --git a/.circleci/config.yml b/.circleci/config.yml
index 544f820d33a..6fd9ede58e0 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,51 +1,114 @@
+parameters:
+
+# v2: 11m.
defaults: &defaults
+ resource_class: large
docker:
- - image: bepsays/ci-goreleaser:1.1800.300
- environment:
- CGO_ENABLED: "0"
-
+ - image: bepsays/ci-hugoreleaser:1.22000.20100
+environment: &buildenv
+ GOMODCACHE: /root/project/gomodcache
version: 2
jobs:
- build:
- <<: *defaults
+ prepare_release:
+ <<: *defaults
+ environment: &buildenv
+ GOMODCACHE: /root/project/gomodcache
steps:
- - checkout:
+ - &remote-docker
+ setup_remote_docker:
+ version: 20.10.14
+ - checkout:
path: hugo
+ - &git-config
+ run:
+ command: |
+ git config --global user.email "bjorn.erik.pedersen+hugoreleaser@gmail.com"
+ git config --global user.name "hugoreleaser"
- run:
- command: |
- git clone git@github.com:gohugoio/hugoDocs.git
- cd hugo
- go mod download
- sleep 5
- go mod verify
- - persist_to_workspace:
- root: .
- paths: .
- release:
- <<: *defaults
+ command: |
+ cd hugo
+ go mod download
+ go run -tags release main.go release --step 1
+ - save_cache:
+ key: git-sha-{{ .Revision }}
+ paths:
+ - hugo
+ - gomodcache
+ build_container1:
+ <<: [*defaults]
+ environment:
+ <<: [*buildenv]
steps:
- - attach_workspace:
- at: /root/project
+ - &restore-cache
+ restore_cache:
+ key: git-sha-{{ .Revision }}
- run:
- command: |
- cd hugo
- git config --global user.email "bjorn.erik.pedersen+hugoreleaser@gmail.com"
- git config --global user.name "hugoreleaser"
- go run -tags release main.go release -r ${CIRCLE_BRANCH}
-
+ no_output_timeout: 20m
+ command: |
+ mkdir -p /tmp/files/dist1
+ cd hugo
+ hugoreleaser build -paths "builds/container1/**" -workers 3 -dist /tmp/files/dist1 -chunks $CIRCLE_NODE_TOTAL -chunk-index $CIRCLE_NODE_INDEX
+ - &persist-workspace
+ persist_to_workspace:
+ root: /tmp/files
+ paths:
+ - dist1
+ - dist2
+ parallelism: 7
+ build_container2:
+ <<: [*defaults]
+ environment:
+ <<: [*buildenv]
+ docker:
+ - image: bepsays/ci-hugoreleaser-linux-arm64:1.22000.20100
+ steps:
+ - *restore-cache
+ - &attach-workspace
+ attach_workspace:
+ at: /tmp/workspace
+ - run:
+ command: |
+ mkdir -p /tmp/files/dist2
+ cd hugo
+ hugoreleaser build -paths "builds/container2/**" -workers 1 -dist /tmp/files/dist2
+ - *persist-workspace
+ archive_and_release:
+ <<: [*defaults]
+ environment:
+ <<: [*buildenv]
+ steps:
+ - *restore-cache
+ - *attach-workspace
+ - *git-config
+ - run:
+ command: |
+ cp -a /tmp/workspace/dist1/. ./hugo/dist
+ cp -a /tmp/workspace/dist2/. ./hugo/dist
+ - run:
+ command: |
+ cd hugo
+ hugoreleaser archive
+ hugoreleaser release
+ go run -tags release main.go release --step 2
workflows:
version: 2
release:
jobs:
- - build:
+ - prepare_release:
filters:
branches:
only: /release-.*/
- - hold:
- type: approval
+ - build_container1:
+ requires:
+ - prepare_release
+ - build_container2:
requires:
- - build
- - release:
+ - prepare_release
+ - archive_and_release:
context: org-global
requires:
- - hold
+ - build_container1
+ - build_container2
+
+
+
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index 86cd2cac4a0..53b8adac43c 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -3,8 +3,13 @@ on:
workflow_dispatch:
schedule:
- cron: '30 1 * * *'
+permissions:
+ contents: read
jobs:
stale:
+ permissions:
+ issues: write
+ pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@08e671be8ac8944d0e132aa71d0ae8ccfb347675
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 1403c4d572a..8a8d02487bb 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -1,30 +1,41 @@
-on: [push, pull_request]
+on:
+ push:
+ branches: [ master ]
+ pull_request:
name: Test
+env:
+ GOPROXY: https://proxy.golang.org
+ GO111MODULE: on
+ DART_SASS_VERSION: 1.56.2
+ DART_SASS_SHA_LINUX: 9e4f455f7b8619959d7878af2862383be58392eb963a14ff87cc512c03701e2a
+ DART_SASS_SHA_MACOS: 5992e979e2c30ec363f8e338822bb2b4443c74232b3340501a76180f5652cb09
+ DART_SASS_SHA_WINDOWS: 8d3d9117c54840e3e6a4919e43acf75ea52f28a64fc87a8e29d80ec72ee36cfb
permissions:
contents: read
jobs:
test:
- env:
- GOPROXY: https://proxy.golang.org
- GO111MODULE: on
strategy:
matrix:
- # Note: We upgraded to Go 1.18 in Hugo v0.95.0
- # Go 1.18 had some breaking changes on the source level which means Hugo cannot be built
- # with older Go versions, but the improvements in Go 1.18 were too good to pass on (e.g. break and continue).
- # Note that you don't need Go (or Go 1.18) to run a pre-built binary.
- go-version: [1.18.x]
+ go-version: [1.19.x,1.20.x]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
+ - name: Checkout code
+ uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8
- name: Install Go
- uses: actions/setup-go@37335c7bb261b353407cff977110895fa0b4f7d8
+ uses: actions/setup-go@268d8c0ca0432bb2cf416faae41297df9d262d7f
with:
go-version: ${{ matrix.go-version }}
+ check-latest: true
+ cache: true
+ cache-dependency-path: |
+ **/go.sum
+ **/go.mod
- name: Install Ruby
- uses: actions/setup-ruby@5f29a1cd8dfebf420691c4c9a0e832e2fae5a526
+ uses: ruby/setup-ruby@ee2113536afb7f793eed4ce60e8d3b26db912da4
with:
- ruby-version: '2.7'
+ ruby-version: '2.7'
+ bundler-cache: true #
- name: Install Python
uses: actions/setup-python@3105fb18c05ddd93efea5f9e0bef7a03a6e9e7df
with:
@@ -33,8 +44,6 @@ jobs:
run: go install github.com/magefile/mage@07afc7d24f4d6d6442305d49552f04fbda5ccb3e
- name: Install asciidoctor
uses: reitzig/actions-asciidoctor@7570212ae20b63653481675fb1ff62d1073632b0
- - name: Checkout code
- uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
- name: Install docutils
run: |
pip install docutils
@@ -49,28 +58,33 @@ jobs:
brew install pandoc
- if: matrix.os == 'windows-latest'
run: |
- choco install pandoc
+ Choco-Install -PackageName pandoc
- run: pandoc -v
+ - if: matrix.os == 'windows-latest'
+ run: |
+ Choco-Install -PackageName mingw -ArgumentList "--version","10.2.0","--allow-downgrade"
- if: matrix.os == 'ubuntu-latest'
name: Install dart-sass-embedded Linux
run: |
- curl -LJO https://github.com/sass/dart-sass-embedded/releases/download/1.0.0-beta.6/sass_embedded-1.0.0-beta.6-linux-x64.tar.gz;
- echo "04fc1e5e28d29a4585a701941b6dace56771d94bfbe7f9e4db28d24417ceeec3 sass_embedded-1.0.0-beta.6-linux-x64.tar.gz" | sha256sum -c;
- tar -xvf sass_embedded-1.0.0-beta.6-linux-x64.tar.gz;
+ 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
- if: matrix.os == 'macos-latest'
name: Install dart-sass-embedded MacOS
run: |
- curl -LJO https://github.com/sass/dart-sass-embedded/releases/download/1.0.0-beta.6/sass_embedded-1.0.0-beta.6-macos-x64.tar.gz;
- echo "b3b984675a9b04aa22f6f2302dda4191b507ac2ca124467db2dfe7e58e72fbad sass_embedded-1.0.0-beta.6-macos-x64.tar.gz" | shasum -a 256 -c;
- tar -xvf sass_embedded-1.0.0-beta.6-macos-x64.tar.gz;
+ 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}-macos-x64.tar.gz";
+ echo "${DART_SASS_SHA_MACOS} sass_embedded-${DART_SASS_VERSION}-macos-x64.tar.gz" | shasum -a 256 -c;
+ tar -xvf "sass_embedded-${DART_SASS_VERSION}-macos-x64.tar.gz";
echo "$GITHUB_WORKSPACE/sass_embedded/" >> $GITHUB_PATH
- if: matrix.os == 'windows-latest'
name: Install dart-sass-embedded Windows
run: |
- curl -LJO https://github.com/sass/dart-sass-embedded/releases/download/1.0.0-beta.6/sass_embedded-1.0.0-beta.6-windows-x64.zip;
- echo "6ae442129dbb3334bc21ef851261da6c0c1b560da790ca2e1350871d00ab816d sass_embedded-1.0.0-beta.6-windows-x64.zip" | sha256sum -c;
- unzip sass_embedded-1.0.0-beta.6-windows-x64.zip;
+ echo "Install Dart Sass version ${env:DART_SASS_VERSION} ..."
+ curl -LJO "https://github.com/sass/dart-sass-embedded/releases/download/${env:DART_SASS_VERSION}/sass_embedded-${env:DART_SASS_VERSION}-windows-x64.zip";
+ Expand-Archive -Path "sass_embedded-${env:DART_SASS_VERSION}-windows-x64.zip" -DestinationPath .;
echo "$env:GITHUB_WORKSPACE/sass_embedded/" | Out-File -FilePath $Env:GITHUB_PATH -Encoding utf-8 -Append
- name: Check
run: |
diff --git a/.gitignore b/.gitignore
index b2aeb914241..00b5b2e8041 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,29 +1,2 @@
-/hugo
-docs/public*
-/.idea
-.vscode/*
-hugo.exe
-*.test
-*.prof
-nohup.out
-cover.out
-*.swp
-*.swo
-.DS_Store
-*~
-vendor/*/
-*.bench
-*.debug
-coverage*.out
-dock.sh
-
-GoBuilds
-dist
-
-hugolib/hugo_stats.json
-resources/sunset.jpg
-
-vendor
-
-.hugo_build.lock
+*.test
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 23de481cd70..06e8a44d909 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,6 +1,6 @@
-# Contributing to Hugo
+>**Note:** We would apprecitate if you hold on with any big refactorings (like renaming deprecated Go packages), mainly because of potential for extra merge work for future coming in in the near future.
-**Note March 16th 2022:** We are currently very constrained on human resources to do code reviews, so we currently require any new Pull Requests to be limited to bug fixes closing an existing issue. Also, we have updated to Go 1.18, but we will currently not accept any generic rewrites, "interface{} to any" replacements and similar.
+# Contributing to Hugo
We welcome contributions to Hugo of any kind including documentation, themes,
organization, tutorials, blog posts, bug reports, issues, feature requests,
@@ -52,8 +52,6 @@ Hugo has become a fully featured static site generator, so any new functionality
If it is of some complexity, the contributor is expected to maintain and support the new feature in the future (answer questions on the forum, fix any bugs etc.).
-It is recommended to open up a discussion on the [Hugo Forum](https://discourse.gohugo.io/) to get feedback on your idea before you begin.
-
Any non-trivial code change needs to update an open [issue](https://github.com/gohugoio/hugo/issues). A non-trivial code change without an issue reference with one of the labels `bug` or `enhancement` will not be merged.
Note that we do not accept new features that require [CGO](https://github.com/golang/go/wiki/cgo).
diff --git a/Dockerfile b/Dockerfile
index 885809fabf2..7d09800358b 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -2,7 +2,7 @@
# Twitter: https://twitter.com/gohugoio
# Website: https://gohugo.io/
-FROM golang:1.18-alpine AS build
+FROM golang:1.19-alpine AS build
# Optionally set HUGO_BUILD_TAGS to "extended" or "nodeploy" when building like so:
# docker build --build-arg HUGO_BUILD_TAGS=extended .
@@ -26,7 +26,7 @@ RUN mage hugo && mage install
# ---
-FROM alpine:3.12
+FROM alpine:3.16
COPY --from=build /go/bin/hugo /usr/bin/hugo
diff --git a/LICENSE b/LICENSE
index 261eeb9e9f8..ad3850b60a3 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
- Copyright [yyyy] [name of copyright owner]
+ Copyright 2022 The Hugo Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/README.md b/README.md
index dc9f3b4457f..5787956028c 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-A Fast and Flexible Static Site Generator built with love by [bep](https://github.com/bep), [spf13](https://spf13.com/) and [friends](https://github.com/gohugoio/hugo/graphs/contributors) in [Go][].
+A Fast and Flexible Static Site Generator built with love by [bep](https://github.com/bep), [spf13](https://spf13.com/) and [friends](https://github.com/gohugoio/hugo/graphs/contributors) in [Go](https://go.dev/).
[Website](https://gohugo.io) |
[Forum](https://discourse.gohugo.io) |
@@ -13,9 +13,19 @@ A Fast and Flexible Static Site Generator built with love by [bep](https://githu
[](https://github.com/gohugoio/hugo/actions?query=workflow%3ATest)
[](https://goreportcard.com/report/github.com/gohugoio/hugo)
+* [Overview](#overview)
+* [Banner Sponsors](#banner-sponsors)
+* [Supported Architectures](#supported-architectures)
+* [Choose How to Install](#choose-how-to-install)
+ * [Install Hugo as Your Site Generator (Binary Install)](#install-hugo-as-your-site-generator-binary-install)
+ * [Build and Install the Binary from Source (Using the Go toolchain)](#build-and-install-the-binary-from-source-using-the-go-toolchain)
+* [The Hugo Documentation](#the-hugo-documentation)
+* [Contributing to Hugo](#contributing-code-to-hugo)
+* [Dependencies](#dependencies)
+
## Overview
-Hugo is a static HTML and CSS website generator written in [Go][].
+Hugo is a static HTML and CSS website generator written in [Go](https://go.dev/).
It is optimized for speed, ease of use, and configurability.
Hugo takes a directory with content and templates and renders them into a full HTML website.
@@ -27,7 +37,17 @@ A good rule of thumb is that each piece of content renders in around 1 milliseco
Hugo is designed to work well for any kind of website including blogs, tumbles, and docs.
-#### Supported Architectures
+## Banner Sponsors
+
+ +
+ +## Supported Architectures Currently, we provide pre-built Hugo binaries for Windows, Linux, FreeBSD, NetBSD, DragonFly BSD, OpenBSD, macOS (Darwin), and [Android](https://gist.github.com/bep/a0d8a26cf6b4f8bc992729b8e50b480b) for x64, i386 and ARM architectures. @@ -38,7 +58,6 @@ Hugo may also be compiled from source wherever the Go compiler tool chain can ru ## Choose How to Install If you want to use Hugo as your site generator, simply install the Hugo binaries. -The Hugo binaries have no external dependencies. To contribute to the Hugo source code or documentation, you should [fork the Hugo GitHub project](https://github.com/gohugoio/hugo#fork-destination-box) and clone it to your local machine. @@ -49,31 +68,24 @@ Building the binaries is an easy task for an experienced `go` getter. Use the [installation instructions in the Hugo documentation](https://gohugo.io/getting-started/installing/). -### Build and Install the Binaries from Source (Advanced Install) +### Build and Install the Binary from Source (Using the Go toolchain) #### Prerequisite Tools -* [Git](https://git-scm.com/) * [Go (we test it with the last 2 major versions; but note that Hugo 0.95.0 only builds with >= Go 1.18.)](https://golang.org/dl/) #### Fetch from GitHub -To fetch and build the source from GitHub: +To fetch, build and install from the Github source: ```bash -mkdir $HOME/src -cd $HOME/src -git clone https://github.com/gohugoio/hugo.git -cd hugo -go install +go install github.com/gohugoio/hugo@latest ``` -**If you are a Windows user, substitute the `$HOME` environment variable above with `%USERPROFILE%`.** - If you want to compile with Sass/SCSS support use `--tags extended` and make sure `CGO_ENABLED=1` is set in your go environment. If you don't want to have CGO enabled, you may use the following command to temporarily enable CGO only for hugo compilation: ```bash -CGO_ENABLED=1 go install --tags extended +CGO_ENABLED=1 go install --tags extended github.com/gohugoio/hugo@latest ``` ## The Hugo Documentation @@ -83,9 +95,7 @@ The Hugo documentation now lives in its own repository, see https://github.com/g ```bash git clone git@github.com:gohugoio/hugo.git ``` -## Contributing to Hugo - -**Note March 16th 2022:** We are currently very constrained on human resources to do code reviews, so we currently require any new Pull Requests to be limited to bug fixes closing an existing issue. Also, we have updated to Go 1.18, but we will currently not accept any generic rewrites, "interface{} to any" replacements and similar. +## Contributing code to Hugo For a complete guide to contributing to Hugo, see the [Contribution Guide](CONTRIBUTING.md). @@ -96,34 +106,18 @@ helping to manage issues, etc. The Hugo community and maintainers are [very active](https://github.com/gohugoio/hugo/pulse/monthly) and helpful, and the project benefits greatly from this activity. -### Asking Support Questions +## Asking Support Questions We have an active [discussion forum](https://discourse.gohugo.io) where users and developers can ask questions. Please don't use the GitHub issue tracker to ask questions. -### Reporting Issues +## Reporting Issues If you believe you have found a defect in Hugo or its documentation, use the GitHub issue tracker to report the problem to the Hugo maintainers. If you're not sure if it's a bug or not, start by asking in the [discussion forum](https://discourse.gohugo.io). When reporting the issue, please provide the version of Hugo in use (`hugo version`). -### Submitting Patches - -The Hugo project welcomes all contributors and contributions regardless of skill or experience level. -If you are interested in helping with the project, we will help you with your contribution. -Hugo is a very active project with many contributions happening daily. - -We want to create the best possible product for our users and the best contribution experience for our developers, -we have a set of guidelines which ensure that all contributions are acceptable. -The guidelines are not intended as a filter or barrier to participation. -If you are unfamiliar with the contribution process, the Hugo team will help you and teach you how to bring your contribution in accordance with the guidelines. - -For a complete guide to contributing code to Hugo, see the [Contribution Guide](CONTRIBUTING.md). - -[Go]: https://golang.org/ -[Hugo Documentation]: https://gohugo.io/overview/introduction/ - ## Dependencies Hugo stands on the shoulder of many great open source libraries. @@ -245,4 +239,3 @@ google.golang.org/grpc="v1.46.0" google.golang.org/protobuf="v1.28.0" gopkg.in/yaml.v2="v2.4.0" ``` - diff --git a/cache/filecache/filecache.go b/cache/filecache/filecache.go index 63d939ef690..88a46621881 100644 --- a/cache/filecache/filecache.go +++ b/cache/filecache/filecache.go @@ -17,7 +17,6 @@ import ( "bytes" "errors" "io" - "io/ioutil" "os" "path/filepath" "strings" @@ -207,7 +206,7 @@ func (c *Cache) GetOrCreateBytes(id string, create func() ([]byte, error)) (Item if r := c.getOrRemove(id); r != nil { defer r.Close() - b, err := ioutil.ReadAll(r) + b, err := io.ReadAll(r) return info, b, err } @@ -242,14 +241,14 @@ func (c *Cache) GetBytes(id string) (ItemInfo, []byte, error) { if r := c.getOrRemove(id); r != nil { defer r.Close() - b, err := ioutil.ReadAll(r) + b, err := io.ReadAll(r) return info, b, err } return info, nil, nil } -// Get gets the file with the given id from the cahce, nil if none found. +// Get gets the file with the given id from the cache, nil if none found. func (c *Cache) Get(id string) (ItemInfo, io.ReadCloser, error) { id = cleanID(id) @@ -314,7 +313,7 @@ func (c *Cache) getString(id string) string { } defer f.Close() - b, _ := ioutil.ReadAll(f) + b, _ := io.ReadAll(f) return string(b) } diff --git a/cache/filecache/filecache_pruner.go b/cache/filecache/filecache_pruner.go index e5e5719725d..b8aa76c150f 100644 --- a/cache/filecache/filecache_pruner.go +++ b/cache/filecache/filecache_pruner.go @@ -18,6 +18,7 @@ import ( "io" "os" + "github.com/gohugoio/hugo/common/herrors" "github.com/gohugoio/hugo/hugofs" "github.com/spf13/afero" @@ -36,7 +37,7 @@ func (c Caches) Prune() (int, error) { counter += count if err != nil { - if os.IsNotExist(err) { + if herrors.IsNotExist(err) { continue } return counter, fmt.Errorf("failed to prune cache %q: %w", k, err) @@ -65,18 +66,24 @@ func (c *Cache) Prune(force bool) (int, error) { if info.IsDir() { f, err := c.Fs.Open(name) + if err != nil { // This cache dir may not exist. return nil } - defer f.Close() _, err = f.Readdirnames(1) + f.Close() if err == io.EOF { // Empty dir. - err = c.Fs.Remove(name) + if name == "." { + // e.g. /_gen/images -- keep it even if empty. + err = nil + } else { + err = c.Fs.Remove(name) + } } - if err != nil && !os.IsNotExist(err) { + if err != nil && !herrors.IsNotExist(err) { return err } @@ -97,7 +104,7 @@ func (c *Cache) Prune(force bool) (int, error) { counter++ } - if err != nil && !os.IsNotExist(err) { + if err != nil && !herrors.IsNotExist(err) { return err } @@ -112,7 +119,7 @@ func (c *Cache) Prune(force bool) (int, error) { func (c *Cache) pruneRootDir(force bool) (int, error) { info, err := c.Fs.Stat(c.pruneAllRootDir) if err != nil { - if os.IsNotExist(err) { + if herrors.IsNotExist(err) { return 0, nil } return 0, err diff --git a/cache/filecache/filecache_test.go b/cache/filecache/filecache_test.go index 47b5a7fcf42..6b96a8601e1 100644 --- a/cache/filecache/filecache_test.go +++ b/cache/filecache/filecache_test.go @@ -17,8 +17,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" - "os" "path/filepath" "strings" "sync" @@ -44,13 +42,8 @@ func TestFileCache(t *testing.T) { t.Parallel() c := qt.New(t) - tempWorkingDir, err := ioutil.TempDir("", "hugo_filecache_test_work") - c.Assert(err, qt.IsNil) - defer os.Remove(tempWorkingDir) - - tempCacheDir, err := ioutil.TempDir("", "hugo_filecache_test_cache") - c.Assert(err, qt.IsNil) - defer os.Remove(tempCacheDir) + tempWorkingDir := t.TempDir() + tempCacheDir := t.TempDir() osfs := afero.NewOsFs() @@ -123,7 +116,7 @@ dir = ":cacheDir/c" io.Closer }{ strings.NewReader(s), - ioutil.NopCloser(nil), + io.NopCloser(nil), }, nil } } @@ -138,7 +131,7 @@ dir = ":cacheDir/c" c.Assert(err, qt.IsNil) c.Assert(r, qt.Not(qt.IsNil)) c.Assert(info.Name, qt.Equals, "a") - b, _ := ioutil.ReadAll(r) + b, _ := io.ReadAll(r) r.Close() c.Assert(string(b), qt.Equals, "abc") @@ -154,7 +147,7 @@ dir = ":cacheDir/c" _, r, err = ca.GetOrCreate("a", rf("bcd")) c.Assert(err, qt.IsNil) - b, _ = ioutil.ReadAll(r) + b, _ = io.ReadAll(r) r.Close() c.Assert(string(b), qt.Equals, "abc") } @@ -173,7 +166,7 @@ dir = ":cacheDir/c" c.Assert(err, qt.IsNil) c.Assert(r, qt.Not(qt.IsNil)) c.Assert(info.Name, qt.Equals, "mykey") - b, _ := ioutil.ReadAll(r) + b, _ := io.ReadAll(r) r.Close() c.Assert(string(b), qt.Equals, "Hugo is great!") @@ -233,7 +226,7 @@ dir = "/cache/c" return hugio.ToReadCloser(strings.NewReader(data)), nil }) c.Assert(err, qt.IsNil) - b, _ := ioutil.ReadAll(r) + b, _ := io.ReadAll(r) r.Close() c.Assert(string(b), qt.Equals, data) // Trigger some expiration. @@ -260,7 +253,7 @@ func TestFileCacheReadOrCreateErrorInRead(t *testing.T) { return errors.New("fail") } - b, _ := ioutil.ReadAll(r) + b, _ := io.ReadAll(r) result = string(b) return nil diff --git a/cache/filecache/integration_test.go b/cache/filecache/integration_test.go new file mode 100644 index 00000000000..26653fc351e --- /dev/null +++ b/cache/filecache/integration_test.go @@ -0,0 +1,97 @@ +// Copyright 2023 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 filecache_test + +import ( + "path/filepath" + "testing" + "time" + + qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/hugolib" +) + +// See issue #10781. That issue wouldn't have been triggered if we kept +// the empty root directories (e.g. _resources/gen/images). +// It's still an upstream Go issue that we also need to handle, but +// this is a test for the first part. +func TestPruneShouldPreserveEmptyCacheRoots(t *testing.T) { + files := ` +-- hugo.toml -- +baseURL = "https://example.com" +-- content/_index.md -- +--- +title: "Home" +--- + +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{T: t, TxtarString: files, RunGC: true, NeedsOsFS: true}, + ).Build() + + _, err := b.H.BaseFs.ResourcesCache.Stat(filepath.Join("_gen", "images")) + + b.Assert(err, qt.IsNil) + +} + +func TestPruneImages(t *testing.T) { + files := ` +-- hugo.toml -- +baseURL = "https://example.com" +[caches] +[caches.images] +maxAge = "200ms" +dir = ":resourceDir/_gen" +-- content/_index.md -- +--- +title: "Home" +--- +-- assets/a/pixel.png -- +iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg== +-- layouts/index.html -- +{{ $img := resources.GetMatch "**.png" }} +{{ $img = $img.Resize "3x3" }} +{{ $img.RelPermalink }} + + + +` + + b := hugolib.NewIntegrationTestBuilder( + hugolib.IntegrationTestConfig{T: t, TxtarString: files, RunGC: true, NeedsOsFS: true}, + ).Build() + + b.Assert(b.GCCount, qt.Equals, 0) + + imagesCacheDir := filepath.Join("_gen", "images") + _, err := b.H.BaseFs.ResourcesCache.Stat(imagesCacheDir) + + b.Assert(err, qt.IsNil) + + // TODO(bep) we need a way to test full rebuilds. + // For now, just sleep a little so the cache elements expires. + time.Sleep(300 * time.Millisecond) + + b.RenameFile("assets/a/pixel.png", "assets/b/pixel2.png").Build() + b.Assert(b.GCCount, qt.Equals, 1) + // Build it again to GC the empty a dir. + b.Build() + _, err = b.H.BaseFs.ResourcesCache.Stat(filepath.Join(imagesCacheDir, "a")) + b.Assert(err, qt.Not(qt.IsNil)) + _, err = b.H.BaseFs.ResourcesCache.Stat(imagesCacheDir) + b.Assert(err, qt.IsNil) + +} diff --git a/codegen/methods.go b/codegen/methods.go index 9bc80cc3e25..65a7cc2b752 100644 --- a/codegen/methods.go +++ b/codegen/methods.go @@ -452,12 +452,16 @@ func collectMethodsRecursive(pkg string, f []*ast.Field) []string { } if ident, ok := m.Type.(*ast.Ident); ok && ident.Obj != nil { - // Embedded interface - methodNames = append( - methodNames, - collectMethodsRecursive( - pkg, - ident.Obj.Decl.(*ast.TypeSpec).Type.(*ast.InterfaceType).Methods.List)...) + switch tt := ident.Obj.Decl.(*ast.TypeSpec).Type.(type) { + case *ast.InterfaceType: + // Embedded interface + methodNames = append( + methodNames, + collectMethodsRecursive( + pkg, + tt.Methods.List)...) + } + } else { // Embedded, but in a different file/package. Return the // package.Name and deal with that later. diff --git a/commands/commandeer.go b/commands/commandeer.go index 5e5e1b3ab97..b77e9e90882 100644 --- a/commands/commandeer.go +++ b/commands/commandeer.go @@ -16,7 +16,7 @@ package commands import ( "errors" "fmt" - "io/ioutil" + "io" "net" "os" "path/filepath" @@ -79,8 +79,8 @@ type commandeer struct { changeDetector *fileChangeDetector // We need to reuse these on server rebuilds. - // These 2 will be different if --renderStaticToDisk is set. publishDirFs afero.Fs + publishDirStaticFs afero.Fs publishDirServerFs afero.Fs h *hugoBuilderCommon @@ -170,6 +170,7 @@ func (c *commandeer) Set(key string, value any) { func (c *commandeer) initFs(fs *hugofs.Fs) error { c.publishDirFs = fs.PublishDir + c.publishDirStaticFs = fs.PublishDirStatic c.publishDirServerFs = fs.PublishDirServer c.DepsCfg.Fs = fs @@ -200,7 +201,7 @@ func newCommandeer(mustHaveConfigFile, failOnInitErr, running bool, h *hugoBuild rebuildDebouncer = debounce.New(4 * time.Second) } - out := ioutil.Discard + out := io.Discard if !h.quiet { out = os.Stdout } @@ -220,7 +221,7 @@ func newCommandeer(mustHaveConfigFile, failOnInitErr, running bool, h *hugoBuild running: running, // This will be replaced later, but we need something to log to before the configuration is read. - logger: loggers.NewLogger(jww.LevelWarn, jww.LevelError, out, ioutil.Discard, running), + logger: loggers.NewLogger(jww.LevelWarn, jww.LevelError, out, io.Discard, running), } return c, c.loadConfig() @@ -382,7 +383,7 @@ func (c *commandeer) loadConfig() error { // Set some commonly used flags c.doLiveReload = c.running && !c.Cfg.GetBool("disableLiveReload") - c.fastRenderMode = c.doLiveReload && !c.Cfg.GetBool("disableFastRender") + c.fastRenderMode = c.running && !c.Cfg.GetBool("disableFastRender") c.showErrorInBrowser = c.doLiveReload && !c.Cfg.GetBool("disableBrowserError") // This is potentially double work, but we need to do this one more time now @@ -407,6 +408,9 @@ func (c *commandeer) loadConfig() error { createMemFs := config.GetBool("renderToMemory") c.renderStaticToDisk = config.GetBool("renderStaticToDisk") + // TODO(bep) we/I really need to look at the config set up, but to prevent changing too much + // we store away the original. + config.Set("publishDirOrig", config.GetString("publishDir")) if createMemFs { // Rendering to memoryFS, publish to Root regardless of publishDir. @@ -426,6 +430,7 @@ func (c *commandeer) loadConfig() error { if c.publishDirFs != nil { // Need to reuse the destination on server rebuilds. fs.PublishDir = c.publishDirFs + fs.PublishDirStatic = c.publishDirStaticFs fs.PublishDirServer = c.publishDirServerFs } else { if c.renderStaticToDisk { diff --git a/commands/commands.go b/commands/commands.go index b81b867f9a0..c1042459d2f 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -177,7 +177,7 @@ Complete documentation is available at https://gohugo.io/.`, }, }) - cc.cmd.PersistentFlags().StringVar(&cc.cfgFile, "config", "", "config file (default is path/config.yaml|json|toml)") + cc.cmd.PersistentFlags().StringVar(&cc.cfgFile, "config", "", "config file (default is hugo.yaml|json|toml)") cc.cmd.PersistentFlags().StringVar(&cc.cfgDir, "configDir", "config", "config dir") cc.cmd.PersistentFlags().BoolVar(&cc.quiet, "quiet", false, "build in quiet mode") diff --git a/commands/commands_test.go b/commands/commands_test.go index 97d81ec6ee9..35621854f76 100644 --- a/commands/commands_test.go +++ b/commands/commands_test.go @@ -15,7 +15,6 @@ package commands import ( "fmt" - "io/ioutil" "os" "path/filepath" "testing" @@ -47,7 +46,7 @@ func TestExecute(t *testing.T) { c.Assert(resp.Err, qt.IsNil) result := resp.Result c.Assert(len(result.Sites) == 1, qt.Equals, true) - c.Assert(len(result.Sites[0].RegularPages()) == 1, qt.Equals, true) + c.Assert(len(result.Sites[0].RegularPages()) == 2, qt.Equals, true) c.Assert(result.Sites[0].Info.Params()["myparam"], qt.Equals, "paramproduction") }) @@ -117,11 +116,13 @@ func TestExecute(t *testing.T) { func checkNewSiteInited(c *qt.C, basepath string) { paths := []string{ - filepath.Join(basepath, "layouts"), - filepath.Join(basepath, "content"), filepath.Join(basepath, "archetypes"), - filepath.Join(basepath, "static"), + filepath.Join(basepath, "assets"), + filepath.Join(basepath, "content"), filepath.Join(basepath, "data"), + filepath.Join(basepath, "layouts"), + filepath.Join(basepath, "static"), + filepath.Join(basepath, "themes"), filepath.Join(basepath, "config.toml"), } @@ -228,7 +229,7 @@ func TestFlags(t *testing.T) { if cmd.getCommand() == nil { continue } - // We are only intereseted in the flag handling here. + // We are only interested in the flag handling here. cmd.getCommand().RunE = noOpRunE } rootCmd := root.getCommand() @@ -362,11 +363,25 @@ weight: 1 Content +`) + + writeFile(t, filepath.Join(dir, contentDir, "hügö.md"), ` +--- +weight: 2 +--- + +This is hügö. + `) writeFile(t, filepath.Join(dir, "layouts", "_default", "single.html"), ` -Single: {{ .Title }} +Single: {{ .Title }}|{{ .Content }} + +`) + + writeFile(t, filepath.Join(dir, "layouts", "404.html"), ` +404: {{ .Title }}|Not Found. `) @@ -386,7 +401,7 @@ PostProcess: {{ $foo.RelPermalink }} func writeFile(t testing.TB, filename, content string) { must(t, os.MkdirAll(filepath.Dir(filename), os.FileMode(0755))) - must(t, ioutil.WriteFile(filename, []byte(content), os.FileMode(0755))) + must(t, os.WriteFile(filename, []byte(content), os.FileMode(0755))) } func must(t testing.TB, err error) { diff --git a/commands/config.go b/commands/config.go index 7fda2d40e97..a5d8aab22fe 100644 --- a/commands/config.go +++ b/commands/config.go @@ -126,6 +126,7 @@ type modMount struct { Lang string `json:"lang,omitempty"` } +// MarshalJSON is for internal use only. func (m *modMounts) MarshalJSON() ([]byte, error) { var mounts []modMount diff --git a/commands/env.go b/commands/env.go index 65808b1be67..0fc509d6d42 100644 --- a/commands/env.go +++ b/commands/env.go @@ -50,6 +50,14 @@ If you add the -v flag, you will get a full dependency list. for _, dep := range deps { jww.FEEDBACK.Printf("%s\n", dep) } + } else { + // These are also included in the GetDependencyList above; + // always print these as these are most likely the most useful to know about. + deps := hugo.GetDependencyListNonGo() + for _, dep := range deps { + jww.FEEDBACK.Printf("%s\n", dep) + } + } return nil diff --git a/commands/hugo.go b/commands/hugo.go index 5169d65a52e..2fa08ec21e5 100644 --- a/commands/hugo.go +++ b/commands/hugo.go @@ -18,7 +18,7 @@ package commands import ( "context" "fmt" - "io/ioutil" + "io" "os" "os/signal" "path/filepath" @@ -138,10 +138,10 @@ func initializeConfig(mustHaveConfigFile, failOnInitErr, running bool, func (c *commandeer) createLogger(cfg config.Provider) (loggers.Logger, error) { var ( - logHandle = ioutil.Discard + logHandle = io.Discard logThreshold = jww.LevelWarn logFile = cfg.GetString("logFile") - outHandle = ioutil.Discard + outHandle = io.Discard stdoutThreshold = jww.LevelWarn ) @@ -157,7 +157,7 @@ func (c *commandeer) createLogger(cfg config.Provider) (loggers.Logger, error) { return nil, newSystemError("Failed to open log file:", logFile, err) } } else { - logHandle, err = ioutil.TempFile("", "hugo") + logHandle, err = os.CreateTemp("", "hugo") if err != nil { return nil, newSystemError(err) } @@ -511,12 +511,15 @@ func (c *commandeer) build() error { c.hugo().PrintProcessingStats(os.Stdout) fmt.Println() - if createCounter, ok := c.publishDirFs.(hugofs.DuplicatesReporter); ok { - dupes := createCounter.ReportDuplicates() - if dupes != "" { - c.logger.Warnln("Duplicate target paths:", dupes) + hugofs.WalkFilesystems(c.publishDirFs, func(fs afero.Fs) bool { + if dfs, ok := fs.(hugofs.DuplicatesReporter); ok { + dupes := dfs.ReportDuplicates() + if dupes != "" { + c.logger.Warnln("Duplicate target paths:", dupes) + } } - } + return false + }) unusedTemplates := c.hugo().Tmpl().(tpl.UnusedTemplatesProvider).UnusedTemplates() for _, unusedTemplate := range unusedTemplates { @@ -576,7 +579,7 @@ func (c *commandeer) serverBuild() error { func (c *commandeer) copyStatic() (map[string]uint64, error) { m, err := c.doWithPublishDirs(c.copyStaticTo) - if err == nil || os.IsNotExist(err) { + if err == nil || herrors.IsNotExist(err) { return m, nil } return m, err @@ -650,10 +653,7 @@ func (c *commandeer) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint6 syncer.NoChmod = c.Cfg.GetBool("noChmod") syncer.ChmodFilter = chmodFilter syncer.SrcFs = fs - syncer.DestFs = c.Fs.PublishDir - if c.renderStaticToDisk { - syncer.DestFs = c.Fs.PublishDirStatic - } + syncer.DestFs = c.Fs.PublishDirStatic // Now that we are using a unionFs for the static directories // We can effectively clean the publishDir on initial sync syncer.Delete = c.Cfg.GetBool("cleanDestinationDir") @@ -899,7 +899,7 @@ func (c *commandeer) newWatcher(pollIntervalStr string, dirList ...string) (*wat } unlock() case err := <-watcher.Errors(): - if err != nil && !os.IsNotExist(err) { + if err != nil && !herrors.IsNotExist(err) { c.logger.Errorln("Error while watching:", err) } } @@ -924,6 +924,7 @@ func (c *commandeer) printChangeDetected(typ string) { const ( configChangeConfig = "config file" configChangeGoMod = "go.mod file" + configChangeGoWork = "go work file" ) func (c *commandeer) handleEvents(watcher *watcher.Batcher, @@ -943,6 +944,9 @@ func (c *commandeer) handleEvents(watcher *watcher.Batcher, if strings.Contains(ev.Name, "go.mod") { configChangeType = configChangeGoMod } + if strings.Contains(ev.Name, ".work") { + configChangeType = configChangeGoWork + } } if !isConfig { // It may be one of the /config folders diff --git a/commands/import_jekyll.go b/commands/import_jekyll.go index 91d5c69fe54..93991121d61 100644 --- a/commands/import_jekyll.go +++ b/commands/import_jekyll.go @@ -17,7 +17,7 @@ import ( "bytes" "errors" "fmt" - "io/ioutil" + "io" "os" "path/filepath" "regexp" @@ -164,7 +164,7 @@ func (i *importCmd) importFromJekyll(cmd *cobra.Command, args []string) error { func (i *importCmd) getJekyllDirInfo(fs afero.Fs, jekyllRoot string) (map[string]bool, bool) { postDirs := make(map[string]bool) hasAnyPost := false - if entries, err := ioutil.ReadDir(jekyllRoot); err == nil { + if entries, err := os.ReadDir(jekyllRoot); err == nil { for _, entry := range entries { if entry.IsDir() { subDir := filepath.Join(jekyllRoot, entry.Name()) @@ -186,7 +186,7 @@ func (i *importCmd) retrieveJekyllPostDir(fs afero.Fs, dir string) (bool, bool) return true, !isEmpty } - if entries, err := ioutil.ReadDir(dir); err == nil { + if entries, err := os.ReadDir(dir); err == nil { for _, entry := range entries { if entry.IsDir() { subDir := filepath.Join(dir, entry.Name()) @@ -247,7 +247,7 @@ func (i *importCmd) loadJekyllConfig(fs afero.Fs, jekyllRoot string) map[string] defer f.Close() - b, err := ioutil.ReadAll(f) + b, err := io.ReadAll(f) if err != nil { return nil } @@ -310,7 +310,7 @@ func (i *importCmd) copyJekyllFilesAndFolders(jekyllRoot, dest string, jekyllPos if err != nil { return err } - entries, err := ioutil.ReadDir(jekyllRoot) + entries, err := os.ReadDir(jekyllRoot) if err != nil { return err } @@ -386,7 +386,7 @@ func convertJekyllPost(path, relPath, targetDir string, draft bool) error { targetParentDir := filepath.Dir(targetFile) os.MkdirAll(targetParentDir, 0777) - contentBytes, err := ioutil.ReadFile(path) + contentBytes, err := os.ReadFile(path) if err != nil { jww.ERROR.Println("Read file error:", path) return err diff --git a/commands/new.go b/commands/new.go index c5b5cd18271..a6c2c8ca1ca 100644 --- a/commands/new.go +++ b/commands/new.go @@ -32,6 +32,7 @@ var _ cmder = (*newCmd)(nil) type newCmd struct { contentEditor string contentType string + force bool *baseBuilderCmd } @@ -54,6 +55,7 @@ Ensure you run this within the root directory of your site.`, cmd.Flags().StringVarP(&cc.contentType, "kind", "k", "", "content type to create") cmd.Flags().StringVar(&cc.contentEditor, "editor", "", "edit new content with this editor, if provided") + cmd.Flags().BoolVarP(&cc.force, "force", "f", false, "overwrite file if it already exists") cmd.AddCommand(b.newNewSiteCmd().getCommand()) cmd.AddCommand(b.newNewThemeCmd().getCommand()) @@ -80,7 +82,7 @@ func (n *newCmd) newContent(cmd *cobra.Command, args []string) error { return newUserError("path needs to be provided") } - return create.NewContent(c.hugo(), n.contentType, args[0]) + return create.NewContent(c.hugo(), n.contentType, args[0], n.force) } func mkdir(x ...string) { diff --git a/commands/new_site.go b/commands/new_site.go index 384c6365bee..fc4127f8b63 100644 --- a/commands/new_site.go +++ b/commands/new_site.go @@ -62,11 +62,12 @@ Use ` + "`hugo new [contentPath]`" + ` to create new content.`, func (n *newSiteCmd) doNewSite(fs *hugofs.Fs, basepath string, force bool) error { archeTypePath := filepath.Join(basepath, "archetypes") dirs := []string{ - filepath.Join(basepath, "layouts"), - filepath.Join(basepath, "content"), archeTypePath, - filepath.Join(basepath, "static"), + filepath.Join(basepath, "assets"), + filepath.Join(basepath, "content"), filepath.Join(basepath, "data"), + filepath.Join(basepath, "layouts"), + filepath.Join(basepath, "static"), filepath.Join(basepath, "themes"), } @@ -82,6 +83,7 @@ func (n *newSiteCmd) doNewSite(fs *hugofs.Fs, basepath string, force bool) error return errors.New(basepath + " already exists and is not empty. See --force.") case !isEmpty && force: + // TODO(bep) eventually rename this to hugo. all := append(dirs, filepath.Join(basepath, "config."+n.configFormat)) for _, path := range all { if exists, _ := helpers.Exists(path, fs.Source); exists { diff --git a/commands/release.go b/commands/release.go index 6decda9ea5d..2072f3eb233 100644 --- a/commands/release.go +++ b/commands/release.go @@ -17,8 +17,6 @@ package commands import ( - "errors" - "github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/releaser" "github.com/spf13/cobra" @@ -29,10 +27,9 @@ var _ cmder = (*releaseCommandeer)(nil) type releaseCommandeer struct { cmd *cobra.Command - version string - - skipPublish bool - try bool + step int + skipPush bool + try bool } func createReleaser() cmder { @@ -50,9 +47,9 @@ func createReleaser() cmder { return r.release() } - r.cmd.PersistentFlags().StringVarP(&r.version, "rel", "r", "", "new release version, i.e. 0.25.1") - r.cmd.PersistentFlags().BoolVarP(&r.skipPublish, "skip-publish", "", false, "skip all publishing pipes of the release") - r.cmd.PersistentFlags().BoolVarP(&r.try, "try", "", false, "simulate a release, i.e. no changes") + r.cmd.PersistentFlags().BoolVarP(&r.skipPush, "skip-push", "", false, "skip pushing to remote") + r.cmd.PersistentFlags().BoolVarP(&r.try, "try", "", false, "no changes") + r.cmd.PersistentFlags().IntVarP(&r.step, "step", "", 0, "step to run (1: set new version 2: prepare next dev version)") return r } @@ -65,8 +62,10 @@ func (c *releaseCommandeer) flagsToConfig(cfg config.Provider) { } func (r *releaseCommandeer) release() error { - if r.version == "" { - return errors.New("must set the --rel flag to the relevant version number") + rel, err := releaser.New(r.skipPush, r.try, r.step) + if err != nil { + return err } - return releaser.New(r.version, r.skipPublish, r.try).Run() + + return rel.Run() } diff --git a/commands/server.go b/commands/server.go index f082164cee9..121a649d4dd 100644 --- a/commands/server.go +++ b/commands/server.go @@ -400,24 +400,31 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string } // Ignore any query params for the operations below. - requestURI := strings.TrimSuffix(r.RequestURI, "?"+r.URL.RawQuery) + requestURI, _ := url.PathUnescape(strings.TrimSuffix(r.RequestURI, "?"+r.URL.RawQuery)) for _, header := range f.c.serverConfig.MatchHeaders(requestURI) { w.Header().Set(header.Key, header.Value) } if redirect := f.c.serverConfig.MatchRedirect(requestURI); !redirect.IsZero() { + // fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name))) doRedirect := true // This matches Netlify's behaviour and is needed for SPA behaviour. // See https://docs.netlify.com/routing/redirects/rewrites-proxies/ if !redirect.Force { path := filepath.Clean(strings.TrimPrefix(requestURI, u.Path)) - fi, err := f.c.hugo().BaseFs.PublishFs.Stat(path) + if root != "" { + path = filepath.Join(root, path) + } + fs := f.c.publishDirServerFs + + fi, err := fs.Stat(path) + if err == nil { if fi.IsDir() { // There will be overlapping directories, so we // need to check for a file. - _, err = f.c.hugo().BaseFs.PublishFs.Stat(filepath.Join(path, "index.html")) + _, err = fs.Stat(filepath.Join(path, "index.html")) doRedirect = err != nil } else { doRedirect = false @@ -426,15 +433,27 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string } if doRedirect { - if redirect.Status == 200 { + switch redirect.Status { + case 404: + w.WriteHeader(404) + file, err := fs.Open(strings.TrimPrefix(redirect.To, u.Path)) + if err == nil { + defer file.Close() + io.Copy(w, file) + } else { + fmt.Fprintln(w, "
2&&D>2&&!e.hidden?(W=p,X=0):W=D>1&&X>1&&Q<6?d:0),f!==c&&($=innerWidth+c*g,M=innerHeight+c,l=-1*c,f=c),s=m[n].getBoundingClientRect(),(B=s.bottom)>=l&&(H=s.top)<=M&&(z=s.right)>=l*g&&(F=s.left)<=$&&(B||z||F||H)&&(i.loadHidden||Z(m[n]))&&(R&&Q<3&&!h&&(D<3||X<4)||Y(m[n],c))){if(at(m[n]),u=!0,Q>9)break}else!u&&R&&!a&&Q<4&&X<4&&D>2&&(L[0]||i.preloadAfterLoad)&&(L[0]||!h&&(B||z||F||H||"auto"!=m[n].getAttribute(i.sizesAttr)))&&(a=L[0]||m[n]);a&&!u&&at(a)}},et=function(t){var e,r=0,o=i.throttleDelay,s=i.ricTimeout,a=function(){e=!1,r=n.now(),t()},c=l&&s>49?function(){l(a,{timeout:s}),s!==i.ricTimeout&&(s=i.ricTimeout)}:C((function(){u(a)}),!0);return function(t){var i;(t=!0===t)&&(s=33),e||(e=!0,(i=o-(n.now()-r))<0&&(i=0),t||i<9?c():u(c,i))}}(tt),nt=function(t){var e=t.target;e._lazyCache?delete e._lazyCache:(G(t),m(e,i.loadedClass),y(e,i.loadingClass),v(e,it),b(e,"lazyloaded"))},rt=C(nt),it=function(t){rt({target:t.target})},ot=function(t){var e,n=t.getAttribute(i.srcsetAttr);(e=i.customMedia[t.getAttribute("data-media")||t.getAttribute("media")])&&t.setAttribute("media",e),n&&t.setAttribute("srcset",n)},st=C((function(t,e,n,r,o){var s,a,c,l,f,d;(f=b(t,"lazybeforeunveil",e)).defaultPrevented||(r&&(n?m(t,i.autosizesClass):t.setAttribute("sizes",r)),a=t.getAttribute(i.srcsetAttr),s=t.getAttribute(i.srcAttr),o&&(l=(c=t.parentNode)&&h.test(c.nodeName||"")),d=e.firesLoad||"src"in t&&(a||s||l),f={target:t},m(t,i.loadingClass),d&&(clearTimeout(P),P=u(G,2500),v(t,it,!0)),l&&p.call(c.getElementsByTagName("source"),ot),a?t.setAttribute("srcset",a):s&&!l&&(K.test(t.nodeName)?function(t,e){var n=t.getAttribute("data-load-mode")||i.iframeLoadMode;0==n?t.contentWindow.location.replace(e):1==n&&(t.src=e)}(t,s):t.src=s),o&&(a||l)&&w(t,{src:s})),t._lazyRace&&delete t._lazyRace,y(t,i.lazyClass),S((function(){var e=t.complete&&t.naturalWidth>1;d&&!e||(e&&m(t,i.fastLoadedClass),nt(f),t._lazyCache=!0,u((function(){"_lazyCache"in t&&delete t._lazyCache}),9)),"lazy"==t.loading&&Q--}),!0)})),at=function(t){if(!t._lazyRace){var e,n=V.test(t.nodeName),r=n&&(t.getAttribute(i.sizesAttr)||t.getAttribute("sizes")),o="auto"==r;(!o&&R||!n||!t.getAttribute("src")&&!t.srcset||t.complete||g(t,i.errorClass)||!g(t,i.lazyClass))&&(e=b(t,"lazyunveilread").detail,o&&T.updateElem(t,!0,t.offsetWidth),t._lazyRace=!0,Q++,st(t,e,o,r,n))}},ut=A((function(){i.loadMode=3,et()})),ct=function(){3==i.loadMode&&(i.loadMode=2),ut()},lt=function(){R||(n.now()-q<999?u(lt,999):(R=!0,i.loadMode=3,et(),a("scroll",ct,!0)))},{_:function(){q=n.now(),r.elements=e.getElementsByClassName(i.lazyClass),L=e.getElementsByClassName(i.lazyClass+" "+i.preloadClass),a("scroll",et,!0),a("resize",et,!0),a("pageshow",(function(t){if(t.persisted){var n=e.querySelectorAll("."+i.loadingClass);n.length&&n.forEach&&c((function(){n.forEach((function(t){t.complete&&at(t)}))}))}})),t.MutationObserver?new MutationObserver(et).observe(o,{childList:!0,subtree:!0,attributes:!0}):(o.addEventListener("DOMNodeInserted",et,!0),o.addEventListener("DOMAttrModified",et,!0),setInterval(et,999)),a("hashchange",et,!0),["focus","mouseover","click","load","transitionend","animationend"].forEach((function(t){e.addEventListener(t,et,!0)})),/d$|^c/.test(e.readyState)?lt():(a("load",lt),e.addEventListener("DOMContentLoaded",et),u(lt,2e4)),r.elements.length?(tt(),S._lsFlush()):et()},checkElems:et,unveil:at,_aLSL:ct}),T=(N=C((function(t,e,n,r){var i,o,s;if(t._lazysizesWidth=r,r+="px",t.setAttribute("sizes",r),h.test(e.nodeName||""))for(o=0,s=(i=e.getElementsByTagName("source")).length;o0&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof e.action?e.action:this.defaultAction,this.target="function"==typeof e.target?e.target:this.defaultTarget,this.text="function"==typeof e.text?e.text:this.defaultText,this.container="object"===r(e.container)?e.container:document.body}},{key:"listenClick",value:function(e){var t=this;this.listener=(0,a.default)(e,"click",(function(e){return t.onClick(e)}))}},{key:"onClick",value:function(e){var t=e.delegateTarget||e.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new s.default({action:this.action(t),target:this.target(t),text:this.text(t),container:this.container,trigger:t,emitter:this})}},{key:"defaultAction",value:function(e){return l("action",e)}},{key:"defaultTarget",value:function(e){var t=l("target",e);if(t)return document.querySelector(t)}},{key:"defaultText",value:function(e){return l("text",e)}},{key:"destroy",value:function(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:["copy","cut"],t="string"==typeof e?[e]:e,n=!!document.queryCommandSupported;return t.forEach((function(e){n=n&&!!document.queryCommandSupported(e)})),n}}]),t}(o.default);function l(e,t){var n="data-clipboard-"+e;if(t.hasAttribute(n))return t.getAttribute(n)}e.exports=u},function(e,t,n){"use strict";var r,i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},s=function(){function e(e,t){for(var n=0;n was loaded but did not call our provided callback"),JSONPScriptError:s("JSONPScriptError","
-
-{{ end }}
-
-
-
- + "> +
+ + {{/* https://www.zachleat.com/web/preload/ */}} + + + + + + {{/* NOTE: the Site's title, and if there is a page title, that is set too */}} +
+ + + {{ hugo.Generator }} + + {{ if hugo.IsProduction }} + + {{ else }} + + {{ end }} + + {{ range .AlternativeOutputFormats -}} + + {{ end -}} + + {{ $isDev := eq hugo.Environment "development" }} + {{ $stylesheet := resources.Get "output/css/app.css" }} + {{ if not $isDev }} + {{ $stylesheet = $stylesheet | minify | fingerprint }} + {{ end }} + {{ with $stylesheet }} + {{ if $isDev }} + + {{ else }} + + {{ end }} + {{ $.Scratch.Set "stylesheet" . }} + {{ end }} + + + + + {{ block "scripts" . }} + {{- partial "site-scripts.html" . -}} + {{ end }} + {{ partial "site-manifest.html" . }} + {{- partial "head-additions.html" . -}} + {{- partial "opengraph/opengraph.html" . -}} + {{- template "_internal/schema.html" . -}} + {{- partial "opengraph/twitter_cards.html" . -}} + + {{ if hugo.IsProduction }} + {{ partial "gtag" . }} + {{ end }} + + + +