mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-25 15:58:53 +00:00
Compare commits
162 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c8232e3bd | |||
| bfc3122f8e | |||
| 00ae8e8c72 | |||
| e423e56273 | |||
| 09eb822822 | |||
| a6e8439176 | |||
| 38f68cd162 | |||
| a67650b6f7 | |||
| 2a060b37a3 | |||
| 97df6be59f | |||
| 9323376dfa | |||
| bf0b140364 | |||
| e9b8bec433 | |||
| 888cc1e61e | |||
| 17765a7451 | |||
| 92de8625c7 | |||
| 7907935a42 | |||
| 02d5ec14f3 | |||
| 26640525a3 | |||
| 488b21d15b | |||
| 4500b0e423 | |||
| 060cce0a91 | |||
| 5608ba1f75 | |||
| 7bf1abfc55 | |||
| 2fedca6c8a | |||
| 07873b74bd | |||
| 8a0ea12d8a | |||
| 6f07e5976d | |||
| 2da4ec5738 | |||
| 6624979e1b | |||
| 983b8d537c | |||
| 6738a3e79d | |||
| 2f7df4b926 | |||
| f0a26cf58e | |||
| 74ce5dc841 | |||
| 54a8f0ce21 | |||
| 38e05bd3c7 | |||
| ebfca61ac4 | |||
| e1917740af | |||
| 27414d43a0 | |||
| c837f36ab4 | |||
| a2f67152b3 | |||
| db083b05f1 | |||
| 758a876f90 | |||
| 19937a20ad | |||
| c1ea22a232 | |||
| 0750a9ec91 | |||
| 90bc1f802a | |||
| 629f84e8ed | |||
| 76ef3f42fa | |||
| 0ccb6cdc04 | |||
| 1f1c62e6c7 | |||
| f1d755965f | |||
| 558f74f009 | |||
| ba03114aa9 | |||
| 3935faa417 | |||
| d4d49e0f0e | |||
| 78178d0c2a | |||
| 68d92ef9dd | |||
| b40f3c7df6 | |||
| 57206e7274 | |||
| b1f8676347 | |||
| 07b2e535be | |||
| f038a51b3e | |||
| b4bff6190c | |||
| d2cebee273 | |||
| be914ff34d | |||
| 48a0fea87a | |||
| 9ca1de09dd | |||
| e62675002e | |||
| 9668759ad8 | |||
| 9e9b1f110c | |||
| dc6a292133 | |||
| 4f92f949ea | |||
| d24ffdde5b | |||
| ca31b95f30 | |||
| 5fed9c591b | |||
| 134e7d1d3d | |||
| 1f48b717c7 | |||
| ada3fceea8 | |||
| 05e23bd55c | |||
| 7afac3f1ad | |||
| a4b17470a8 | |||
| 4d5e173cf8 | |||
| 4271b6be0f | |||
| 0567a3e6f1 | |||
| 632ad74fc5 | |||
| b1de03f398 | |||
| 312735366b | |||
| 2b2f2b75ef | |||
| 0d6e593ffb | |||
| 7023cf0f07 | |||
| 9dfa9e70e6 | |||
| 3f217fd664 | |||
| 92684f9a26 | |||
| fce8d82b79 | |||
| 4a502f7eb4 | |||
| be1dbba0f7 | |||
| 1007bcdf49 | |||
| 1904ba40e8 | |||
| 6a8b25e291 | |||
| a322282e70 | |||
| 6bc0d745a5 | |||
| 218690328c | |||
| 673c634131 | |||
| 21a41003c4 | |||
| 1736ef7459 | |||
| d4be1643a0 | |||
| f27e578718 | |||
| d310595a2b | |||
| 049dd1d7e0 | |||
| 189b72331e | |||
| b2b7bfdd3a | |||
| 80e69344da | |||
| 7508a16470 | |||
| a75a659f6f | |||
| 03b88c6ddb | |||
| 7ee0f70507 | |||
| 564bae06f6 | |||
| bf14d0cb26 | |||
| f521336c81 | |||
| c4fe45ff4c | |||
| 554aa58db6 | |||
| 36bf3cb98c | |||
| ea0ba9751d | |||
| 929b91fe75 | |||
| db3bb623d4 | |||
| f5fd7ec8cc | |||
| 16406d9d77 | |||
| e75784930d | |||
| b8ab5c9be6 | |||
| cf7023cb90 | |||
| fd0185a84a | |||
| 6da1d8f370 | |||
| 3f8434a62f | |||
| c9f7ebf004 | |||
| eceeb19751 | |||
| 621194a319 | |||
| 48eec2a4e6 | |||
| a118cb4138 | |||
| e8cc785a58 | |||
| 2658a71e1b | |||
| 8406a3e9dc | |||
| f54ba6f9ef | |||
| 4019b177e6 | |||
| 5dbc29dc6c | |||
| 5ada27bf65 | |||
| 60b176cb57 | |||
| afe5b6d7df | |||
| 168d375784 | |||
| 4a53fd56b7 | |||
| fc6aabe938 | |||
| 9cc65757a1 | |||
| 4835f9e898 | |||
| 5bdda0bdbf | |||
| 9679443c15 | |||
| 2d1681d915 | |||
| 68f67c9aeb | |||
| 43ea2cd660 | |||
| f1491c900e | |||
| 639073e4fe | |||
| 21d9057dbf |
@@ -4,7 +4,7 @@ parameters:
|
||||
defaults: &defaults
|
||||
resource_class: large
|
||||
docker:
|
||||
- image: bepsays/ci-hugoreleaser:1.22200.20000
|
||||
- image: bepsays/ci-hugoreleaser:1.22200.20200
|
||||
environment: &buildenv
|
||||
GOMODCACHE: /root/project/gomodcache
|
||||
version: 2
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
environment:
|
||||
<<: [*buildenv]
|
||||
docker:
|
||||
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22200.20000
|
||||
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22200.20200
|
||||
steps:
|
||||
- *restore-cache
|
||||
- &attach-workspace
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
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
|
||||
GOPROXY: https://proxy.golang.org
|
||||
GO111MODULE: on
|
||||
DART_SASS_VERSION: 1.62.1
|
||||
DART_SASS_SHA_LINUX: 3574da75a7322a539034648b8ff84ff2cca162eb924d72b663d718cd3936f075
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
@@ -18,56 +18,69 @@ jobs:
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753
|
||||
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@036ef458ddccddb148a2b9fb67e95a22fdbf728b
|
||||
with:
|
||||
ruby-version: '2.7'
|
||||
bundler-cache: true #
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@3105fb18c05ddd93efea5f9e0bef7a03a6e9e7df
|
||||
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@7570212ae20b63653481675fb1ff62d1073632b0
|
||||
- name: Install docutils
|
||||
run: |
|
||||
pip install docutils
|
||||
rst2html.py --version
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Install pandoc on Linux
|
||||
run: |
|
||||
- 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
|
||||
- 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
|
||||
|
||||
+116
-103
@@ -1,119 +1,132 @@
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
branches: [master]
|
||||
pull_request:
|
||||
name: Test
|
||||
env:
|
||||
GOPROXY: https://proxy.golang.org
|
||||
GO111MODULE: on
|
||||
SASS_VERSION: 1.63.2
|
||||
DART_SASS_SHA_LINUX: 3ea33c95ad5c35fda6e9a0956199eef38a398f496cfb8750e02479d7d1dd42af
|
||||
DART_SASS_SHA_MACOS: 11c70f259836b250b44a9cb57fed70e030f21f45069b467d371685855f1eb4f0
|
||||
DART_SASS_SHA_WINDOWS: cd8cd36a619dd8e27f93d3186c52d70eb7d69472aa6c85f5094b29693e773f64
|
||||
GOPROXY: https://proxy.golang.org
|
||||
GO111MODULE: on
|
||||
SASS_VERSION: 1.63.2
|
||||
DART_SASS_SHA_LINUX: 3ea33c95ad5c35fda6e9a0956199eef38a398f496cfb8750e02479d7d1dd42af
|
||||
DART_SASS_SHA_MACOS: 11c70f259836b250b44a9cb57fed70e030f21f45069b467d371685855f1eb4f0
|
||||
DART_SASS_SHA_WINDOWS: cd8cd36a619dd8e27f93d3186c52d70eb7d69472aa6c85f5094b29693e773f64
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.21.x,1.22.x]
|
||||
go-version: [1.21.x, 1.22.x]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753
|
||||
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@036ef458ddccddb148a2b9fb67e95a22fdbf728b
|
||||
with:
|
||||
ruby-version: '2.7'
|
||||
bundler-cache: true #
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@3105fb18c05ddd93efea5f9e0bef7a03a6e9e7df
|
||||
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@7570212ae20b63653481675fb1ff62d1073632b0
|
||||
- name: Install docutils
|
||||
run: |
|
||||
pip install docutils
|
||||
rst2html.py --version
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Install pandoc on Linux
|
||||
run: |
|
||||
- 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 pandoc
|
||||
- run: pandoc -v
|
||||
- if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
choco install mingw
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Install dart-sass Linux
|
||||
run: |
|
||||
echo "Install Dart Sass version ${SASS_VERSION} ..."
|
||||
curl -LJO "https://github.com/sass/dart-sass/releases/download/${SASS_VERSION}/dart-sass-${SASS_VERSION}-linux-x64.tar.gz";
|
||||
echo "${DART_SASS_SHA_LINUX} dart-sass-${SASS_VERSION}-linux-x64.tar.gz" | sha256sum -c;
|
||||
tar -xvf "dart-sass-${SASS_VERSION}-linux-x64.tar.gz";
|
||||
echo "$GOBIN"
|
||||
echo "$GITHUB_WORKSPACE/dart-sass/" >> $GITHUB_PATH
|
||||
- if: matrix.os == 'macos-latest'
|
||||
name: Install dart-sass MacOS
|
||||
run: |
|
||||
echo "Install Dart Sass version ${SASS_VERSION} ..."
|
||||
curl -LJO "https://github.com/sass/dart-sass/releases/download/${SASS_VERSION}/dart-sass-${SASS_VERSION}-macos-x64.tar.gz";
|
||||
echo "${DART_SASS_SHA_MACOS} dart-sass-${SASS_VERSION}-macos-x64.tar.gz" | shasum -a 256 -c;
|
||||
tar -xvf "dart-sass-${SASS_VERSION}-macos-x64.tar.gz";
|
||||
echo "$GITHUB_WORKSPACE/dart-sass/" >> $GITHUB_PATH
|
||||
- if: matrix.os == 'windows-latest'
|
||||
name: Install dart-sass Windows
|
||||
run: |
|
||||
echo "Install Dart Sass version ${env:SASS_VERSION} ..."
|
||||
curl -LJO "https://github.com/sass/dart-sass/releases/download/${env:SASS_VERSION}/dart-sass-${env:SASS_VERSION}-windows-x64.zip";
|
||||
Expand-Archive -Path "dart-sass-${env:SASS_VERSION}-windows-x64.zip" -DestinationPath .;
|
||||
echo "$env:GITHUB_WORKSPACE/dart-sass/" | Out-File -FilePath $Env:GITHUB_PATH -Encoding utf-8 -Append
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Install staticcheck
|
||||
run: go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Run staticcheck
|
||||
run: staticcheck ./...
|
||||
- if: matrix.os != 'windows-latest'
|
||||
name: Check
|
||||
run: |
|
||||
sass --version;
|
||||
mage -v check;
|
||||
env:
|
||||
HUGO_BUILD_TAGS: extended
|
||||
- if: matrix.os == 'windows-latest'
|
||||
# See issue #11052. We limit the build to regular test (no -race flag) on Windows for now.
|
||||
name: Test
|
||||
run: |
|
||||
mage -v test;
|
||||
env:
|
||||
HUGO_BUILD_TAGS: extended
|
||||
- name: Build tags
|
||||
run: |
|
||||
go install -tags extended,nodeploy
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Build for dragonfly
|
||||
run: |
|
||||
go install
|
||||
env:
|
||||
GOARCH: amd64
|
||||
GOOS: dragonfly
|
||||
- if: matrix.os == 'macos-latest'
|
||||
run: |
|
||||
brew install pandoc
|
||||
- if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
choco install pandoc
|
||||
- run: pandoc -v
|
||||
- if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
choco install mingw
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Install dart-sass Linux
|
||||
run: |
|
||||
echo "Install Dart Sass version ${SASS_VERSION} ..."
|
||||
curl -LJO "https://github.com/sass/dart-sass/releases/download/${SASS_VERSION}/dart-sass-${SASS_VERSION}-linux-x64.tar.gz";
|
||||
echo "${DART_SASS_SHA_LINUX} dart-sass-${SASS_VERSION}-linux-x64.tar.gz" | sha256sum -c;
|
||||
tar -xvf "dart-sass-${SASS_VERSION}-linux-x64.tar.gz";
|
||||
echo "$GOBIN"
|
||||
echo "$GITHUB_WORKSPACE/dart-sass/" >> $GITHUB_PATH
|
||||
- if: matrix.os == 'macos-latest'
|
||||
name: Install dart-sass MacOS
|
||||
run: |
|
||||
echo "Install Dart Sass version ${SASS_VERSION} ..."
|
||||
curl -LJO "https://github.com/sass/dart-sass/releases/download/${SASS_VERSION}/dart-sass-${SASS_VERSION}-macos-x64.tar.gz";
|
||||
echo "${DART_SASS_SHA_MACOS} dart-sass-${SASS_VERSION}-macos-x64.tar.gz" | shasum -a 256 -c;
|
||||
tar -xvf "dart-sass-${SASS_VERSION}-macos-x64.tar.gz";
|
||||
echo "$GITHUB_WORKSPACE/dart-sass/" >> $GITHUB_PATH
|
||||
- if: matrix.os == 'windows-latest'
|
||||
name: Install dart-sass Windows
|
||||
run: |
|
||||
echo "Install Dart Sass version ${env:SASS_VERSION} ..."
|
||||
curl -LJO "https://github.com/sass/dart-sass/releases/download/${env:SASS_VERSION}/dart-sass-${env:SASS_VERSION}-windows-x64.zip";
|
||||
Expand-Archive -Path "dart-sass-${env:SASS_VERSION}-windows-x64.zip" -DestinationPath .;
|
||||
echo "$env:GITHUB_WORKSPACE/dart-sass/" | Out-File -FilePath $Env:GITHUB_PATH -Encoding utf-8 -Append
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Install staticcheck
|
||||
run: go install honnef.co/go/tools/cmd/staticcheck@latest
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Run staticcheck
|
||||
run: staticcheck ./...
|
||||
- if: matrix.os != 'windows-latest'
|
||||
name: Check
|
||||
run: |
|
||||
sass --version;
|
||||
mage -v check;
|
||||
env:
|
||||
HUGO_BUILD_TAGS: extended
|
||||
- if: matrix.os == 'windows-latest'
|
||||
# See issue #11052. We limit the build to regular test (no -race flag) on Windows for now.
|
||||
name: Test
|
||||
run: |
|
||||
mage -v test;
|
||||
env:
|
||||
HUGO_BUILD_TAGS: extended
|
||||
- name: Build tags
|
||||
run: |
|
||||
go install -tags extended,nodeploy
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Build for dragonfly
|
||||
run: |
|
||||
go install
|
||||
env:
|
||||
GOARCH: amd64
|
||||
GOOS: dragonfly
|
||||
|
||||
Vendored
+3
-1
@@ -295,6 +295,8 @@ func (c *Cache) start() func() {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
c.adjustCurrentMaxSize()
|
||||
// Reset the ticker to avoid drift.
|
||||
ticker.Reset(c.opts.CheckInterval)
|
||||
case <-quit:
|
||||
ticker.Stop()
|
||||
return
|
||||
@@ -338,7 +340,7 @@ func GetOrCreatePartition[K comparable, V any](c *Cache, name string, opts Optio
|
||||
return p.(*Partition[K, V])
|
||||
}
|
||||
|
||||
// At this point, we don't know the the number of partitions or their configuration, but
|
||||
// At this point, we don't know the number of partitions or their configuration, but
|
||||
// this will be re-adjusted later.
|
||||
const numberOfPartitionsEstimate = 10
|
||||
maxSize := opts.CalculateMaxSize(c.opts.MaxSize / numberOfPartitionsEstimate)
|
||||
|
||||
+16
-12
@@ -48,6 +48,7 @@ import (
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
"github.com/gohugoio/hugo/resources/kinds"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -482,46 +483,47 @@ Complete documentation is available at https://gohugo.io/.`
|
||||
|
||||
// Configure persistent flags
|
||||
cmd.PersistentFlags().StringVarP(&r.source, "source", "s", "", "filesystem path to read files relative from")
|
||||
cmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
|
||||
_ = cmd.MarkFlagDirname("source")
|
||||
cmd.PersistentFlags().StringP("destination", "d", "", "filesystem path to write files to")
|
||||
cmd.PersistentFlags().SetAnnotation("destination", cobra.BashCompSubdirsInDir, []string{})
|
||||
_ = cmd.MarkFlagDirname("destination")
|
||||
|
||||
cmd.PersistentFlags().StringVarP(&r.environment, "environment", "e", "", "build environment")
|
||||
_ = cmd.RegisterFlagCompletionFunc("environment", cobra.NoFileCompletions)
|
||||
cmd.PersistentFlags().StringP("themesDir", "", "", "filesystem path to themes directory")
|
||||
_ = cmd.MarkFlagDirname("themesDir")
|
||||
cmd.PersistentFlags().StringP("ignoreVendorPaths", "", "", "ignores any _vendor for module paths matching the given Glob pattern")
|
||||
_ = cmd.RegisterFlagCompletionFunc("ignoreVendorPaths", cobra.NoFileCompletions)
|
||||
cmd.PersistentFlags().String("clock", "", "set the clock used by Hugo, e.g. --clock 2021-11-06T22:30:00.00+09:00")
|
||||
_ = cmd.RegisterFlagCompletionFunc("clock", cobra.NoFileCompletions)
|
||||
|
||||
cmd.PersistentFlags().StringVar(&r.cfgFile, "config", "", "config file (default is hugo.yaml|json|toml)")
|
||||
_ = cmd.MarkFlagFilename("config", config.ValidConfigFileExtensions...)
|
||||
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().BoolVar(&r.renderToMemory, "renderToMemory", false, "render to memory (mostly useful when running the server)")
|
||||
|
||||
// Set bash-completion
|
||||
_ = cmd.PersistentFlags().SetAnnotation("config", cobra.BashCompFilenameExt, config.ValidConfigFileExtensions)
|
||||
|
||||
cmd.PersistentFlags().BoolVarP(&r.verbose, "verbose", "v", false, "verbose output")
|
||||
cmd.PersistentFlags().BoolVarP(&r.debug, "debug", "", false, "debug output")
|
||||
cmd.PersistentFlags().StringVar(&r.logLevel, "logLevel", "", "log level (debug|info|warn|error)")
|
||||
_ = cmd.RegisterFlagCompletionFunc("logLevel", cobra.FixedCompletions([]string{"debug", "info", "warn", "error"}, cobra.ShellCompDirectiveNoFileComp))
|
||||
cmd.Flags().BoolVarP(&r.buildWatch, "watch", "w", false, "watch filesystem for changes and recreate as needed")
|
||||
|
||||
// Configure local flags
|
||||
applyLocalFlagsBuild(cmd, r)
|
||||
|
||||
// Set bash-completion.
|
||||
// Each flag must first be defined before using the SetAnnotation() call.
|
||||
_ = cmd.Flags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// A sub set of the complete build flags. These flags are used by new and mod.
|
||||
func applyLocalFlagsBuildConfig(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.Flags().StringSliceP("theme", "t", []string{}, "themes to use (located in /themes/THEMENAME/)")
|
||||
_ = cmd.MarkFlagDirname("theme")
|
||||
cmd.Flags().StringVarP(&r.baseURL, "baseURL", "b", "", "hostname (and path) to the root, e.g. https://spf13.com/")
|
||||
cmd.Flags().StringP("cacheDir", "", "", "filesystem path to cache directory")
|
||||
_ = cmd.Flags().SetAnnotation("cacheDir", cobra.BashCompSubdirsInDir, []string{})
|
||||
_ = cmd.MarkFlagDirname("cacheDir")
|
||||
cmd.Flags().StringP("contentDir", "c", "", "filesystem path to content directory")
|
||||
_ = cmd.Flags().SetAnnotation("theme", cobra.BashCompSubdirsInDir, []string{"themes"})
|
||||
cmd.Flags().StringSliceP("renderSegments", "", []string{}, "named segments to render (configured in the segments config)")
|
||||
}
|
||||
|
||||
// Flags needed to do a build (used by hugo and hugo server commands)
|
||||
@@ -534,8 +536,10 @@ func applyLocalFlagsBuild(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.Flags().BoolP("ignoreCache", "", false, "ignores the cache directory")
|
||||
cmd.Flags().Bool("enableGitInfo", false, "add Git revision, date, author, and CODEOWNERS info to the pages")
|
||||
cmd.Flags().StringP("layoutDir", "l", "", "filesystem path to layout directory")
|
||||
_ = cmd.MarkFlagDirname("layoutDir")
|
||||
cmd.Flags().BoolVar(&r.gc, "gc", false, "enable to run some cleanup tasks (remove unused cache files) after the build")
|
||||
cmd.Flags().StringVar(&r.poll, "poll", "", "set this to a poll interval, e.g --poll 700ms, to use a poll based approach to watch for file system changes")
|
||||
_ = cmd.RegisterFlagCompletionFunc("poll", cobra.NoFileCompletions)
|
||||
cmd.Flags().Bool("panicOnWarning", false, "panic on first WARNING log")
|
||||
cmd.Flags().Bool("templateMetrics", false, "display metrics about template executions")
|
||||
cmd.Flags().Bool("templateMetricsHints", false, "calculate some improvement hints when combined with --templateMetrics")
|
||||
@@ -558,8 +562,8 @@ func applyLocalFlagsBuild(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.Flags().MarkHidden("profile-mutex")
|
||||
|
||||
cmd.Flags().StringSlice("disableKinds", []string{}, "disable different kind of pages (home, RSS etc.)")
|
||||
_ = cmd.RegisterFlagCompletionFunc("disableKinds", cobra.FixedCompletions(kinds.AllKinds, cobra.ShellCompDirectiveNoFileComp))
|
||||
cmd.Flags().Bool("minify", false, "minify any supported output format (HTML, XML etc.)")
|
||||
_ = cmd.Flags().SetAnnotation("destination", cobra.BashCompSubdirsInDir, []string{})
|
||||
}
|
||||
|
||||
func (r *rootCommand) timeTrack(start time.Time, name string) {
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/gohugoio/hugo/modules"
|
||||
"github.com/gohugoio/hugo/parser"
|
||||
"github.com/gohugoio/hugo/parser/metadecoders"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// newConfigCommand creates a new config command and its subcommands.
|
||||
@@ -112,7 +113,9 @@ func (c *configCommand) Init(cd *simplecobra.Commandeer) error {
|
||||
cmd.Short = "Print the site configuration"
|
||||
cmd.Long = `Print the site configuration, both default and custom settings.`
|
||||
cmd.Flags().StringVar(&c.format, "format", "toml", "preferred file format (toml, yaml or json)")
|
||||
_ = cmd.RegisterFlagCompletionFunc("format", cobra.FixedCompletions([]string{"toml", "yaml", "json"}, cobra.ShellCompDirectiveNoFileComp))
|
||||
cmd.Flags().StringVar(&c.lang, "lang", "", "the language to display config for. Defaults to the first language defined.")
|
||||
_ = cmd.RegisterFlagCompletionFunc("lang", cobra.NoFileCompletions)
|
||||
applyLocalFlagsBuildConfig(cmd, c.r)
|
||||
|
||||
return nil
|
||||
@@ -223,6 +226,7 @@ func (c *configMountsCommand) Init(cd *simplecobra.Commandeer) error {
|
||||
c.r = cd.Root.Command.(*rootCommand)
|
||||
cmd := cd.CobraCommand
|
||||
cmd.Short = "Print the configured file mounts"
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
applyLocalFlagsBuildConfig(cmd, c.r)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ to use JSON for the front matter.`,
|
||||
return c.convertContents(metadecoders.JSON)
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
},
|
||||
},
|
||||
&simpleCommand{
|
||||
@@ -57,6 +58,7 @@ to use TOML for the front matter.`,
|
||||
return c.convertContents(metadecoders.TOML)
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
},
|
||||
},
|
||||
&simpleCommand{
|
||||
@@ -68,6 +70,7 @@ to use YAML for the front matter.`,
|
||||
return c.convertContents(metadecoders.YAML)
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -108,6 +111,7 @@ func (c *convertCommand) Init(cd *simplecobra.Commandeer) error {
|
||||
See convert's subcommands toJSON, toTOML and toYAML for more information.`
|
||||
|
||||
cmd.PersistentFlags().StringVarP(&c.outputDir, "output", "o", "", "filesystem path to write files to")
|
||||
_ = cmd.MarkFlagDirname("output")
|
||||
cmd.PersistentFlags().BoolVar(&c.unsafe, "unsafe", false, "enable less safe operations, please backup first")
|
||||
|
||||
cmd.RunE = nil
|
||||
|
||||
@@ -60,13 +60,17 @@ documentation.
|
||||
return deployer.Deploy(ctx)
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
cmd.Flags().String("target", "", "target deployment from deployments section in config file; defaults to the first one")
|
||||
_ = cmd.RegisterFlagCompletionFunc("target", cobra.NoFileCompletions)
|
||||
cmd.Flags().Bool("confirm", false, "ask for confirmation before making changes to the target")
|
||||
cmd.Flags().Bool("dryRun", false, "dry run")
|
||||
cmd.Flags().Bool("force", false, "force upload of all files")
|
||||
cmd.Flags().Bool("invalidateCDN", deployconfig.DefaultConfig.InvalidateCDN, "invalidate the CDN cache listed in the deployment target")
|
||||
cmd.Flags().Int("maxDeletes", deployconfig.DefaultConfig.MaxDeletes, "maximum # of files to delete, or -1 to disable")
|
||||
_ = cmd.RegisterFlagCompletionFunc("maxDeletes", cobra.NoFileCompletions)
|
||||
cmd.Flags().Int("workers", deployconfig.DefaultConfig.Workers, "number of workers to transfer files. defaults to 10")
|
||||
_ = cmd.RegisterFlagCompletionFunc("workers", cobra.NoFileCompletions)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
|
||||
"github.com/bep/simplecobra"
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newEnvCommand() simplecobra.Commander {
|
||||
@@ -47,6 +48,9 @@ func newEnvCommand() simplecobra.Commander {
|
||||
}
|
||||
return nil
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,5 +63,8 @@ func newVersionCmd() simplecobra.Commander {
|
||||
},
|
||||
short: "Print Hugo version and environment info",
|
||||
long: "Print Hugo version and environment info. This is useful in Hugo bug reports.",
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+9
-4
@@ -75,9 +75,13 @@ See https://xyproto.github.io/splash/docs/all.html for a preview of the availabl
|
||||
return nil
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
cmd.PersistentFlags().StringVar(&style, "style", "friendly", "highlighter style (see https://xyproto.github.io/splash/docs/)")
|
||||
_ = cmd.RegisterFlagCompletionFunc("style", cobra.NoFileCompletions)
|
||||
cmd.PersistentFlags().StringVar(&highlightStyle, "highlightStyle", "", "style used for highlighting lines (see https://github.com/alecthomas/chroma)")
|
||||
_ = cmd.RegisterFlagCompletionFunc("highlightStyle", cobra.NoFileCompletions)
|
||||
cmd.PersistentFlags().StringVar(&linesStyle, "linesStyle", "", "style used for line numbers (see https://github.com/alecthomas/chroma)")
|
||||
_ = cmd.RegisterFlagCompletionFunc("linesStyle", cobra.NoFileCompletions)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -115,9 +119,9 @@ See https://xyproto.github.io/splash/docs/all.html for a preview of the availabl
|
||||
return nil
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
cmd.PersistentFlags().StringVar(&genmandir, "dir", "man/", "the directory to write the man pages.")
|
||||
// For bash-completion
|
||||
cmd.PersistentFlags().SetAnnotation("dir", cobra.BashCompSubdirsInDir, []string{})
|
||||
_ = cmd.MarkFlagDirname("dir")
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -172,9 +176,9 @@ url: %s
|
||||
return nil
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
cmd.PersistentFlags().StringVar(&gendocdir, "dir", "/tmp/hugodoc/", "the directory to write the doc.")
|
||||
// For bash-completion
|
||||
cmd.PersistentFlags().SetAnnotation("dir", cobra.BashCompSubdirsInDir, []string{})
|
||||
_ = cmd.MarkFlagDirname("dir")
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -227,6 +231,7 @@ url: %s
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.Hidden = true
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
cmd.PersistentFlags().StringVarP(&docsHelperTarget, "dir", "", "docs/data", "data dir")
|
||||
},
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ Import from Jekyll requires two paths, e.g. ` + "`hugo import jekyll jekyll_root
|
||||
return c.importFromJekyll(args)
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
cmd.Flags().BoolVar(&c.force, "force", false, "allow import into non-empty target directory")
|
||||
},
|
||||
},
|
||||
|
||||
+14
-2
@@ -23,10 +23,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/bep/simplecobra"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
"github.com/gohugoio/hugo/resources/resource"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// newListCommand creates a new list command and its subcommands.
|
||||
@@ -46,7 +46,7 @@ func newListCommand() *listCommand {
|
||||
|
||||
list := func(cd *simplecobra.Commandeer, r *rootCommand, shouldInclude func(page.Page) bool, opts ...any) error {
|
||||
bcfg := hugolib.BuildCfg{SkipRender: true}
|
||||
cfg := config.New()
|
||||
cfg := flagsToCfg(cd, nil)
|
||||
for i := 0; i < len(opts); i += 2 {
|
||||
cfg.Set(opts[i].(string), opts[i+1])
|
||||
}
|
||||
@@ -103,6 +103,9 @@ func newListCommand() *listCommand {
|
||||
"buildExpired", true,
|
||||
)
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
},
|
||||
},
|
||||
&simpleCommand{
|
||||
name: "future",
|
||||
@@ -120,6 +123,9 @@ func newListCommand() *listCommand {
|
||||
"buildDrafts", true,
|
||||
)
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
},
|
||||
},
|
||||
&simpleCommand{
|
||||
name: "expired",
|
||||
@@ -137,6 +143,9 @@ func newListCommand() *listCommand {
|
||||
"buildDrafts", true,
|
||||
)
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
},
|
||||
},
|
||||
&simpleCommand{
|
||||
name: "all",
|
||||
@@ -148,6 +157,9 @@ func newListCommand() *listCommand {
|
||||
}
|
||||
return list(cd, r, shouldInclude, "buildDrafts", true, "buildFuture", true, "buildExpired", true)
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ removed from Hugo, but we need to test this out in "real life" to get a feel of
|
||||
so this may/will change in future versions of Hugo.
|
||||
`,
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
applyLocalFlagsBuildConfig(cmd, r)
|
||||
},
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
@@ -89,6 +90,7 @@ so this may/will change in future versions of Hugo.
|
||||
inside a subfolder on GitHub, as one example.
|
||||
`,
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
applyLocalFlagsBuildConfig(cmd, r)
|
||||
},
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
@@ -108,6 +110,7 @@ so this may/will change in future versions of Hugo.
|
||||
short: "Verify dependencies.",
|
||||
long: `Verify checks that the dependencies of the current module, which are stored in a local downloaded source cache, have not been modified since being downloaded.`,
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
applyLocalFlagsBuildConfig(cmd, r)
|
||||
cmd.Flags().BoolVarP(&clean, "clean", "", false, "delete module cache for dependencies that fail verification")
|
||||
},
|
||||
@@ -127,6 +130,7 @@ so this may/will change in future versions of Hugo.
|
||||
Note that for vendored modules, that is the version listed and not the one from go.mod.
|
||||
`,
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
applyLocalFlagsBuildConfig(cmd, r)
|
||||
cmd.Flags().BoolVarP(&clean, "clean", "", false, "delete module cache for dependencies that fail verification")
|
||||
},
|
||||
@@ -144,8 +148,10 @@ Note that for vendored modules, that is the version listed and not the one from
|
||||
short: "Delete the Hugo Module cache for the current project.",
|
||||
long: `Delete the Hugo Module cache for the current project.`,
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
applyLocalFlagsBuildConfig(cmd, r)
|
||||
cmd.Flags().StringVarP(&pattern, "pattern", "", "", `pattern matching module paths to clean (all if not set), e.g. "**hugo*"`)
|
||||
_ = cmd.RegisterFlagCompletionFunc("pattern", cobra.NoFileCompletions)
|
||||
cmd.Flags().BoolVarP(&all, "all", "", false, "clean entire module cache")
|
||||
},
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
@@ -167,6 +173,7 @@ Note that for vendored modules, that is the version listed and not the one from
|
||||
name: "tidy",
|
||||
short: "Remove unused entries in go.mod and go.sum.",
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
applyLocalFlagsBuildConfig(cmd, r)
|
||||
},
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
@@ -184,6 +191,7 @@ Note that for vendored modules, that is the version listed and not the one from
|
||||
If a module is vendored, that is where Hugo will look for it's dependencies.
|
||||
`,
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
applyLocalFlagsBuildConfig(cmd, r)
|
||||
},
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
@@ -225,6 +233,7 @@ Run "go help get" for more information. All flags available for "go get" is also
|
||||
` + commonUsageMod,
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.DisableFlagParsing = true
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
},
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
// We currently just pass on the flags we get to Go and
|
||||
|
||||
@@ -60,8 +60,15 @@ Ensure you run this within the root directory of your site.`,
|
||||
return create.NewContent(h, contentType, args[0], force)
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return []string{}, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
return []string{}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveFilterDirs
|
||||
}
|
||||
cmd.Flags().StringVarP(&contentType, "kind", "k", "", "content type to create")
|
||||
cmd.Flags().String("editor", "", "edit new content with this editor, if provided")
|
||||
_ = cmd.RegisterFlagCompletionFunc("editor", cobra.NoFileCompletions)
|
||||
cmd.Flags().BoolVarP(&force, "force", "f", false, "overwrite file if it already exists")
|
||||
applyLocalFlagsBuildConfig(cmd, r)
|
||||
},
|
||||
@@ -103,8 +110,15 @@ Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
|
||||
return nil
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) != 0 {
|
||||
return []string{}, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
return []string{}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveFilterDirs
|
||||
}
|
||||
cmd.Flags().BoolVarP(&force, "force", "f", false, "init inside non-empty directory")
|
||||
cmd.Flags().StringVar(&format, "format", "toml", "preferred file format (toml, yaml or json)")
|
||||
_ = cmd.RegisterFlagCompletionFunc("format", cobra.FixedCompletions([]string{"toml", "yaml", "json"}, cobra.ShellCompDirectiveNoFileComp))
|
||||
},
|
||||
},
|
||||
&simpleCommand{
|
||||
@@ -137,6 +151,9 @@ according to your needs.`,
|
||||
|
||||
return nil
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -43,9 +43,11 @@ func newReleaseCommand() simplecobra.Commander {
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.Hidden = true
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
cmd.PersistentFlags().BoolVarP(&skipPush, "skip-push", "", false, "skip pushing to remote")
|
||||
cmd.PersistentFlags().BoolVarP(&try, "try", "", false, "no changes")
|
||||
cmd.PersistentFlags().IntVarP(&step, "step", "", 0, "step to run (1: set new version 2: prepare next dev version)")
|
||||
_ = cmd.RegisterFlagCompletionFunc("step", cobra.FixedCompletions([]string{"1", "2"}, cobra.ShellCompDirectiveNoFileComp))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+22
-20
@@ -123,6 +123,7 @@ func newServerCommand() *serverCommand {
|
||||
return mclib.RunMain()
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
cmd.Flags().BoolVar(&uninstall, "uninstall", false, "Uninstall the local CA (but do not delete it).")
|
||||
},
|
||||
},
|
||||
@@ -236,9 +237,8 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
|
||||
listener := f.c.serverPorts[i].ln
|
||||
logger := f.c.r.logger
|
||||
|
||||
r.Printf("Environment: %q\n", f.c.hugoTry().Deps.Site.Hugo().Environment)
|
||||
|
||||
if i == 0 {
|
||||
r.Printf("Environment: %q\n", f.c.hugoTry().Deps.Site.Hugo().Environment)
|
||||
mainTarget := "disk"
|
||||
if f.c.r.renderToMemory {
|
||||
mainTarget = "memory"
|
||||
@@ -307,7 +307,7 @@ func (f *fileServer) createEndpoint(i int) (*http.ServeMux, net.Listener, string
|
||||
if redirect := 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.
|
||||
// This matches Netlify's behavior and is needed for SPA behavior.
|
||||
// See https://docs.netlify.com/routing/redirects/rewrites-proxies/
|
||||
if !redirect.Force {
|
||||
path := filepath.Clean(strings.TrimPrefix(requestURI, baseURL.Path()))
|
||||
@@ -512,9 +512,9 @@ func (c *serverCommand) Init(cd *simplecobra.Commandeer) error {
|
||||
cmd.Long = `Hugo provides its own webserver which builds and serves the site.
|
||||
While hugo server is high performance, it is a webserver with limited options.
|
||||
|
||||
'hugo server' will by default write and server files from disk, but you can
|
||||
render to memory by using the '--renderToMemory' flag. This can be faster
|
||||
in some cases, but it will consume more memory.
|
||||
The ` + "`" + `hugo server` + "`" + ` command will by default write and serve files from disk, but
|
||||
you can render to memory by using the ` + "`" + `--renderToMemory` + "`" + ` flag. This can be
|
||||
faster in some cases, but it will consume more memory.
|
||||
|
||||
By default hugo will also watch your files for any changes you make and
|
||||
automatically rebuild the site. It will then live reload any open browser pages
|
||||
@@ -523,10 +523,15 @@ of a second, you will be able to save and see your changes nearly instantly.`
|
||||
cmd.Aliases = []string{"serve"}
|
||||
|
||||
cmd.Flags().IntVarP(&c.serverPort, "port", "p", 1313, "port on which the server will listen")
|
||||
_ = cmd.RegisterFlagCompletionFunc("port", cobra.NoFileCompletions)
|
||||
cmd.Flags().IntVar(&c.liveReloadPort, "liveReloadPort", -1, "port for live reloading (i.e. 443 in HTTPS proxy situations)")
|
||||
_ = cmd.RegisterFlagCompletionFunc("liveReloadPort", cobra.NoFileCompletions)
|
||||
cmd.Flags().StringVarP(&c.serverInterface, "bind", "", "127.0.0.1", "interface to which the server will bind")
|
||||
_ = cmd.RegisterFlagCompletionFunc("bind", cobra.NoFileCompletions)
|
||||
cmd.Flags().StringVarP(&c.tlsCertFile, "tlsCertFile", "", "", "path to TLS certificate file")
|
||||
_ = cmd.MarkFlagFilename("tlsCertFile", "pem")
|
||||
cmd.Flags().StringVarP(&c.tlsKeyFile, "tlsKeyFile", "", "", "path to TLS key file")
|
||||
_ = cmd.MarkFlagFilename("tlsKeyFile", "pem")
|
||||
cmd.Flags().BoolVar(&c.tlsAuto, "tlsAuto", false, "generate and use locally-trusted certificates.")
|
||||
cmd.Flags().BoolVar(&c.pprof, "pprof", false, "enable the pprof server (port 8080)")
|
||||
cmd.Flags().BoolVarP(&c.serverWatch, "watch", "w", true, "watch filesystem for changes and recreate as needed")
|
||||
@@ -538,9 +543,6 @@ of a second, you will be able to save and see your changes nearly instantly.`
|
||||
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")
|
||||
|
||||
cmd.Flags().SetAnnotation("tlsCertFile", cobra.BashCompSubdirsInDir, []string{})
|
||||
cmd.Flags().SetAnnotation("tlsKeyFile", cobra.BashCompSubdirsInDir, []string{})
|
||||
|
||||
r := cd.Root.Command.(*rootCommand)
|
||||
applyLocalFlagsBuild(cmd, r)
|
||||
|
||||
@@ -566,7 +568,7 @@ func (c *serverCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.setBaseURLsInConfig(); err != nil {
|
||||
if err := c.setServerInfoInConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -611,15 +613,15 @@ func (c *serverCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *serverCommand) setBaseURLsInConfig() error {
|
||||
func (c *serverCommand) setServerInfoInConfig() error {
|
||||
if len(c.serverPorts) == 0 {
|
||||
panic("no server ports set")
|
||||
}
|
||||
return c.withConfE(func(conf *commonConfig) error {
|
||||
for i, language := range conf.configs.Languages {
|
||||
isMultiHost := conf.configs.IsMultihost
|
||||
isMultihost := conf.configs.IsMultihost
|
||||
var serverPort int
|
||||
if isMultiHost {
|
||||
if isMultihost {
|
||||
serverPort = c.serverPorts[i].p
|
||||
} else {
|
||||
serverPort = c.serverPorts[0].p
|
||||
@@ -638,7 +640,8 @@ func (c *serverCommand) setBaseURLsInConfig() error {
|
||||
if c.liveReloadPort != -1 {
|
||||
baseURLLiveReload, _ = baseURLLiveReload.WithPort(c.liveReloadPort)
|
||||
}
|
||||
langConfig.C.SetBaseURL(baseURL, baseURLLiveReload)
|
||||
langConfig.C.SetServerInfo(baseURL, baseURLLiveReload, c.serverInterface)
|
||||
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -737,9 +740,9 @@ func (c *serverCommand) createServerPorts(cd *simplecobra.Commandeer) error {
|
||||
flags := cd.CobraCommand.Flags()
|
||||
var cerr error
|
||||
c.withConf(func(conf *commonConfig) {
|
||||
isMultiHost := conf.configs.IsMultihost
|
||||
isMultihost := conf.configs.IsMultihost
|
||||
c.serverPorts = make([]serverPortListener, 1)
|
||||
if isMultiHost {
|
||||
if isMultihost {
|
||||
if !c.serverAppend {
|
||||
cerr = errors.New("--appendPort=false not supported when in multihost mode")
|
||||
return
|
||||
@@ -852,7 +855,7 @@ func (c *serverCommand) serve() error {
|
||||
h *hugolib.HugoSites
|
||||
)
|
||||
err := c.withConfE(func(conf *commonConfig) error {
|
||||
isMultiHost := conf.configs.IsMultihost
|
||||
isMultihost := conf.configs.IsMultihost
|
||||
var err error
|
||||
h, err = c.r.HugFromConfig(conf)
|
||||
if err != nil {
|
||||
@@ -862,7 +865,7 @@ func (c *serverCommand) serve() error {
|
||||
// We need the server to share the same logger as the Hugo build (for error counts etc.)
|
||||
c.r.logger = h.Log
|
||||
|
||||
if isMultiHost {
|
||||
if isMultihost {
|
||||
for _, l := range conf.configs.ConfigLangs() {
|
||||
baseURLs = append(baseURLs, l.BaseURL())
|
||||
roots = append(roots, l.Language().Lang)
|
||||
@@ -1005,7 +1008,6 @@ func (c *serverCommand) serve() error {
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
c.r.Println("Error:", err)
|
||||
}
|
||||
@@ -1087,7 +1089,7 @@ func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
|
||||
|
||||
fromPath := ev.Name
|
||||
|
||||
relPath, found := sourceFs.MakePathRelative(fromPath)
|
||||
relPath, found := sourceFs.MakePathRelative(fromPath, true)
|
||||
|
||||
if !found {
|
||||
// Not member of this virtual host.
|
||||
|
||||
@@ -111,11 +111,29 @@ func (i HugoInfo) Deps() []*Dependency {
|
||||
return i.deps
|
||||
}
|
||||
|
||||
// Deprecated: Use hugo.IsMultihost instead.
|
||||
func (i HugoInfo) IsMultiHost() bool {
|
||||
Deprecate("hugo.IsMultiHost", "Use hugo.IsMultihost instead.", "v0.124.0")
|
||||
return i.conf.IsMultihost()
|
||||
}
|
||||
|
||||
// IsMultihost reports whether each configured language has a unique baseURL.
|
||||
func (i HugoInfo) IsMultihost() bool {
|
||||
return i.conf.IsMultihost()
|
||||
}
|
||||
|
||||
// IsMultilingual reports whether there are two or more configured languages.
|
||||
func (i HugoInfo) IsMultilingual() bool {
|
||||
return i.conf.IsMultilingual()
|
||||
}
|
||||
|
||||
// ConfigProvider represents the config options that are relevant for HugoInfo.
|
||||
type ConfigProvider interface {
|
||||
Environment() string
|
||||
Running() bool
|
||||
WorkingDir() string
|
||||
IsMultihost() bool
|
||||
IsMultilingual() bool
|
||||
}
|
||||
|
||||
// NewInfo creates a new Hugo Info object.
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
// 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 hugo_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
)
|
||||
|
||||
func TestIsMultilingualAndIsMultihost(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
|
||||
defaultContentLanguageInSubdir = true
|
||||
[languages.de]
|
||||
baseURL = 'https://de.example.org/'
|
||||
[languages.en]
|
||||
baseURL = 'https://en.example.org/'
|
||||
-- content/_index.md --
|
||||
---
|
||||
title: home
|
||||
---
|
||||
-- layouts/index.html --
|
||||
multilingual={{ hugo.IsMultilingual }}
|
||||
multihost={{ hugo.IsMultihost }}
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/de/index.html",
|
||||
"multilingual=true",
|
||||
"multihost=true",
|
||||
)
|
||||
b.AssertFileContent("public/en/index.html",
|
||||
"multilingual=true",
|
||||
"multihost=true",
|
||||
)
|
||||
|
||||
files = strings.ReplaceAll(files, "baseURL = 'https://de.example.org/'", "")
|
||||
files = strings.ReplaceAll(files, "baseURL = 'https://en.example.org/'", "")
|
||||
|
||||
b = hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/de/index.html",
|
||||
"multilingual=true",
|
||||
"multihost=false",
|
||||
)
|
||||
b.AssertFileContent("public/en/index.html",
|
||||
"multilingual=true",
|
||||
"multihost=false",
|
||||
)
|
||||
|
||||
files = strings.ReplaceAll(files, "[languages.de]", "")
|
||||
files = strings.ReplaceAll(files, "[languages.en]", "")
|
||||
|
||||
b = hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/en/index.html",
|
||||
"multilingual=false",
|
||||
"multihost=false",
|
||||
)
|
||||
}
|
||||
@@ -65,9 +65,11 @@ func TestDeprecationLogLevelFromVersion(t *testing.T) {
|
||||
}
|
||||
|
||||
type testConfig struct {
|
||||
environment string
|
||||
running bool
|
||||
workingDir string
|
||||
environment string
|
||||
running bool
|
||||
workingDir string
|
||||
multihost bool
|
||||
multilingual bool
|
||||
}
|
||||
|
||||
func (c testConfig) Environment() string {
|
||||
@@ -81,3 +83,11 @@ func (c testConfig) Running() bool {
|
||||
func (c testConfig) WorkingDir() string {
|
||||
return c.workingDir
|
||||
}
|
||||
|
||||
func (c testConfig) IsMultihost() bool {
|
||||
return c.multihost
|
||||
}
|
||||
|
||||
func (c testConfig) IsMultilingual() bool {
|
||||
return c.multilingual
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ package hugo
|
||||
// This should be the only one.
|
||||
var CurrentVersion = Version{
|
||||
Major: 0,
|
||||
Minor: 123,
|
||||
Minor: 125,
|
||||
PatchLevel: 0,
|
||||
Suffix: "-DEV",
|
||||
}
|
||||
|
||||
+83
-22
@@ -18,9 +18,11 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
)
|
||||
|
||||
var defaultPathParser PathParser
|
||||
@@ -50,19 +52,54 @@ func NormalizePathStringBasic(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
// Parse parses component c with path s into Path using Hugo's content path rules.
|
||||
func (parser PathParser) Parse(c, s string) *Path {
|
||||
p, err := parser.parse(c, s)
|
||||
// ParseIdentity parses component c with path s into a StringIdentity.
|
||||
func (pp *PathParser) ParseIdentity(c, s string) identity.StringIdentity {
|
||||
p := pp.parsePooled(c, s)
|
||||
defer putPath(p)
|
||||
return identity.StringIdentity(p.IdentifierBase())
|
||||
}
|
||||
|
||||
// ParseBaseAndBaseNameNoIdentifier parses component c with path s into a base and a base name without any identifier.
|
||||
func (pp *PathParser) ParseBaseAndBaseNameNoIdentifier(c, s string) (string, string) {
|
||||
p := pp.parsePooled(c, s)
|
||||
defer putPath(p)
|
||||
return p.Base(), p.BaseNameNoIdentifier()
|
||||
}
|
||||
|
||||
func (pp *PathParser) parsePooled(c, s string) *Path {
|
||||
s = NormalizePathStringBasic(s)
|
||||
p := getPath()
|
||||
p.component = c
|
||||
p, err := pp.doParse(c, s, p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Parse parses component c with path s into Path using Hugo's content path rules.
|
||||
func (pp *PathParser) Parse(c, s string) *Path {
|
||||
p, err := pp.parse(c, s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (pp *PathParser) newPath(component string) *Path {
|
||||
return &Path{
|
||||
component: component,
|
||||
posContainerLow: -1,
|
||||
posContainerHigh: -1,
|
||||
posSectionHigh: -1,
|
||||
posIdentifierLanguage: -1,
|
||||
}
|
||||
}
|
||||
|
||||
func (pp *PathParser) parse(component, s string) (*Path, error) {
|
||||
ss := NormalizePathStringBasic(s)
|
||||
|
||||
p, err := pp.doParse(component, ss)
|
||||
p, err := pp.doParse(component, ss, pp.newPath(component))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -70,7 +107,7 @@ func (pp *PathParser) parse(component, s string) (*Path, error) {
|
||||
if s != ss {
|
||||
var err error
|
||||
// Preserve the original case for titles etc.
|
||||
p.unnormalized, err = pp.doParse(component, s)
|
||||
p.unnormalized, err = pp.doParse(component, s, pp.newPath(component))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -82,15 +119,7 @@ func (pp *PathParser) parse(component, s string) (*Path, error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (pp *PathParser) doParse(component, s string) (*Path, error) {
|
||||
p := &Path{
|
||||
component: component,
|
||||
posContainerLow: -1,
|
||||
posContainerHigh: -1,
|
||||
posSectionHigh: -1,
|
||||
posIdentifierLanguage: -1,
|
||||
}
|
||||
|
||||
func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
|
||||
hasLang := pp.LanguageIndex != nil
|
||||
hasLang = hasLang && (component == files.ComponentFolderContent || component == files.ComponentFolderLayouts)
|
||||
|
||||
@@ -210,7 +239,7 @@ const (
|
||||
// E.g. /blog/my-post.md
|
||||
PathTypeContentSingle
|
||||
|
||||
// All bewlow are bundled content files.
|
||||
// All below are bundled content files.
|
||||
|
||||
// Leaf bundles, e.g. /blog/my-post/index.md
|
||||
PathTypeLeaf
|
||||
@@ -220,6 +249,7 @@ const (
|
||||
)
|
||||
|
||||
type Path struct {
|
||||
// Note: Any additions to this struct should also be added to the pathPool.
|
||||
s string
|
||||
|
||||
posContainerLow int
|
||||
@@ -239,6 +269,37 @@ type Path struct {
|
||||
unnormalized *Path
|
||||
}
|
||||
|
||||
var pathPool = &sync.Pool{
|
||||
New: func() any {
|
||||
p := &Path{}
|
||||
p.reset()
|
||||
return p
|
||||
},
|
||||
}
|
||||
|
||||
func getPath() *Path {
|
||||
return pathPool.Get().(*Path)
|
||||
}
|
||||
|
||||
func putPath(p *Path) {
|
||||
p.reset()
|
||||
pathPool.Put(p)
|
||||
}
|
||||
|
||||
func (p *Path) reset() {
|
||||
p.s = ""
|
||||
p.posContainerLow = -1
|
||||
p.posContainerHigh = -1
|
||||
p.posSectionHigh = -1
|
||||
p.component = ""
|
||||
p.bundleType = 0
|
||||
p.identifiers = p.identifiers[:0]
|
||||
p.posIdentifierLanguage = -1
|
||||
p.disabled = false
|
||||
p.trimLeadingSlash = false
|
||||
p.unnormalized = nil
|
||||
}
|
||||
|
||||
// TrimLeadingSlash returns a copy of the Path with the leading slash removed.
|
||||
func (p Path) TrimLeadingSlash() *Path {
|
||||
p.trimLeadingSlash = true
|
||||
@@ -252,9 +313,9 @@ func (p *Path) norm(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
// IdentifierBase satifies identity.Identity.
|
||||
// IdentifierBase satisfies identity.Identity.
|
||||
func (p *Path) IdentifierBase() string {
|
||||
return p.Base()[1:]
|
||||
return p.Base()
|
||||
}
|
||||
|
||||
// Component returns the component for this path (e.g. "content").
|
||||
@@ -307,7 +368,7 @@ func (p *Path) Name() string {
|
||||
return p.s
|
||||
}
|
||||
|
||||
// Name returns the last element of path withhout any extension.
|
||||
// Name returns the last element of path without any extension.
|
||||
func (p *Path) NameNoExt() string {
|
||||
if i := p.identifierIndex(0); i != -1 {
|
||||
return p.s[p.posContainerHigh : p.identifiers[i].Low-1]
|
||||
@@ -315,7 +376,7 @@ func (p *Path) NameNoExt() string {
|
||||
return p.s[p.posContainerHigh:]
|
||||
}
|
||||
|
||||
// Name returns the last element of path withhout any language identifier.
|
||||
// Name returns the last element of path without any language identifier.
|
||||
func (p *Path) NameNoLang() string {
|
||||
i := p.identifierIndex(p.posIdentifierLanguage)
|
||||
if i == -1 {
|
||||
@@ -325,7 +386,7 @@ func (p *Path) NameNoLang() string {
|
||||
return p.s[p.posContainerHigh:p.identifiers[i].Low-1] + p.s[p.identifiers[i].High:]
|
||||
}
|
||||
|
||||
// BaseNameNoIdentifier returns the logcical base name for a resource without any idenifier (e.g. no extension).
|
||||
// BaseNameNoIdentifier returns the logical base name for a resource without any identifier (e.g. no extension).
|
||||
// For bundles this will be the containing directory's name, e.g. "blog".
|
||||
func (p *Path) BaseNameNoIdentifier() string {
|
||||
if p.IsBundle() {
|
||||
@@ -334,7 +395,7 @@ func (p *Path) BaseNameNoIdentifier() string {
|
||||
return p.NameNoIdentifier()
|
||||
}
|
||||
|
||||
// NameNoIdentifier returns the last element of path withhout any identifier (e.g. no extension).
|
||||
// NameNoIdentifier returns the last element of path without any identifier (e.g. no extension).
|
||||
func (p *Path) NameNoIdentifier() string {
|
||||
if len(p.identifiers) > 0 {
|
||||
return p.s[p.posContainerHigh : p.identifiers[len(p.identifiers)-1].Low-1]
|
||||
@@ -374,7 +435,7 @@ func (p *Path) PathNoIdentifier() string {
|
||||
return p.base(false, false)
|
||||
}
|
||||
|
||||
// PathRel returns the path relativeto the given owner.
|
||||
// PathRel returns the path relative to the given owner.
|
||||
func (p *Path) PathRel(owner *Path) string {
|
||||
ob := owner.Base()
|
||||
if !strings.HasSuffix(ob, "/") {
|
||||
|
||||
@@ -349,3 +349,9 @@ func TestHasExt(t *testing.T) {
|
||||
c.Assert(HasExt("/a/b/c"), qt.IsFalse)
|
||||
c.Assert(HasExt("/a/b.c/d"), qt.IsFalse)
|
||||
}
|
||||
|
||||
func BenchmarkParseIdentity(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
testParser.ParseIdentity(files.ComponentFolderAssets, "/a/b.css")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +99,14 @@ type Unwrapper interface {
|
||||
Unwrapv() any
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying value of v if it implements Unwrapper, otherwise v is returned.
|
||||
func Unwrapv(v any) any {
|
||||
if u, ok := v.(Unwrapper); ok {
|
||||
return u.Unwrapv()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// LowHigh is typically used to represent a slice boundary.
|
||||
type LowHigh struct {
|
||||
Low int
|
||||
|
||||
@@ -39,6 +39,7 @@ import (
|
||||
"github.com/gohugoio/hugo/config/services"
|
||||
"github.com/gohugoio/hugo/deploy/deployconfig"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugolib/segments"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
"github.com/gohugoio/hugo/markup/markup_config"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
@@ -103,9 +104,11 @@ type Config struct {
|
||||
RootConfig
|
||||
|
||||
// Author information.
|
||||
// Deprecated: Use taxonomies instead.
|
||||
Author map[string]any
|
||||
|
||||
// Social links.
|
||||
// Deprecated: Use .Site.Params instead.
|
||||
Social map[string]string
|
||||
|
||||
// The build configuration section contains build-related configuration options.
|
||||
@@ -137,6 +140,9 @@ type Config struct {
|
||||
// a slice of page matcher and params to apply to those pages.
|
||||
Cascade *config.ConfigNamespace[[]page.PageMatcherParamsConfig, map[page.PageMatcher]maps.Params] `mapstructure:"-"`
|
||||
|
||||
// The segments defines segments for the site. Used for partial/segmented builds.
|
||||
Segments *config.ConfigNamespace[map[string]segments.SegmentConfig, segments.Segments] `mapstructure:"-"`
|
||||
|
||||
// Menu configuration.
|
||||
// <docsmeta>{"refs": ["config:languages:menus"] }</docsmeta>
|
||||
Menus *config.ConfigNamespace[map[string]navigation.MenuConfig, navigation.Menus] `mapstructure:"-"`
|
||||
@@ -364,6 +370,7 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
|
||||
CreateTitle: helpers.GetTitleFunc(c.TitleCaseStyle),
|
||||
IsUglyURLSection: isUglyURL,
|
||||
IgnoreFile: ignoreFile,
|
||||
SegmentFilter: c.Segments.Config.Get(func(s string) { logger.Warnf("Render segment %q not found in configuration", s) }, c.RootConfig.RenderSegments...),
|
||||
MainSections: c.MainSections,
|
||||
Clock: clock,
|
||||
transientErr: transientErr,
|
||||
@@ -393,6 +400,7 @@ type ConfigCompiled struct {
|
||||
Timeout time.Duration
|
||||
BaseURL urls.BaseURL
|
||||
BaseURLLiveReload urls.BaseURL
|
||||
ServerInterface string
|
||||
KindOutputFormats map[string]output.Formats
|
||||
DisabledKinds map[string]bool
|
||||
DisabledLanguages map[string]bool
|
||||
@@ -400,6 +408,7 @@ type ConfigCompiled struct {
|
||||
CreateTitle func(s string) string
|
||||
IsUglyURLSection func(section string) bool
|
||||
IgnoreFile func(filename string) bool
|
||||
SegmentFilter segments.SegmentFilter
|
||||
MainSections []string
|
||||
Clock time.Time
|
||||
|
||||
@@ -426,9 +435,10 @@ func (c *ConfigCompiled) IsMainSectionsSet() bool {
|
||||
}
|
||||
|
||||
// This is set after the config is compiled by the server command.
|
||||
func (c *ConfigCompiled) SetBaseURL(baseURL, baseURLLiveReload urls.BaseURL) {
|
||||
func (c *ConfigCompiled) SetServerInfo(baseURL, baseURLLiveReload urls.BaseURL, serverInterface string) {
|
||||
c.BaseURL = baseURL
|
||||
c.BaseURLLiveReload = baseURLLiveReload
|
||||
c.ServerInterface = serverInterface
|
||||
}
|
||||
|
||||
// RootConfig holds all the top-level configuration options in Hugo
|
||||
@@ -472,6 +482,10 @@ type RootConfig struct {
|
||||
// A list of languages to disable.
|
||||
DisableLanguages []string
|
||||
|
||||
// The named segments to render.
|
||||
// This needs to match the name of the segment in the segments configuration.
|
||||
RenderSegments []string
|
||||
|
||||
// Disable the injection of the Hugo generator tag on the home page.
|
||||
DisableHugoGeneratorInject bool
|
||||
|
||||
@@ -538,6 +552,9 @@ type RootConfig struct {
|
||||
// Note that this currently only works for English, but you can provide your own title in the content file's front matter.
|
||||
PluralizeListTitles bool
|
||||
|
||||
// Whether to capitalize automatic page titles, applicable to section, taxonomy, and term pages.
|
||||
CapitalizeListTitles bool
|
||||
|
||||
// Make all relative URLs absolute using the baseURL.
|
||||
// <docsmeta>{"identifiers": ["baseURL"] }</docsmeta>
|
||||
CanonifyURLs bool
|
||||
@@ -663,11 +680,18 @@ type Configs struct {
|
||||
// All below is set in Init.
|
||||
Languages langs.Languages
|
||||
LanguagesDefaultFirst langs.Languages
|
||||
ContentPathParser paths.PathParser
|
||||
ContentPathParser *paths.PathParser
|
||||
|
||||
configLangs []config.AllProvider
|
||||
}
|
||||
|
||||
func (c *Configs) Validate(logger loggers.Logger) error {
|
||||
for p := range c.Base.Cascade.Config {
|
||||
page.CheckCascadePattern(logger, p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// transientErr returns the last transient error found during config compilation.
|
||||
func (c *Configs) transientErr() error {
|
||||
for _, l := range c.LanguageConfigSlice {
|
||||
@@ -735,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}
|
||||
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 {
|
||||
@@ -816,7 +840,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||
langConfigMap := make(map[string]*Config)
|
||||
|
||||
languagesConfig := cfg.GetStringMap("languages")
|
||||
var isMultiHost bool
|
||||
var isMultihost bool
|
||||
|
||||
if err := all.CompileConfig(logger); err != nil {
|
||||
return nil, err
|
||||
@@ -853,7 +877,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||
}
|
||||
if kk == "baseurl" {
|
||||
// baseURL configure don the language level is a multihost setup.
|
||||
isMultiHost = true
|
||||
isMultihost = true
|
||||
}
|
||||
mergedConfig.Set(kk, vv)
|
||||
rootv := cfg.Get(kk)
|
||||
@@ -903,7 +927,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||
}
|
||||
|
||||
// Adjust Goldmark config defaults for multilingual, single-host sites.
|
||||
if len(languagesConfig) > 1 && !isMultiHost && !clone.Markup.Goldmark.DuplicateResourceFiles {
|
||||
if len(languagesConfig) > 1 && !isMultihost && !clone.Markup.Goldmark.DuplicateResourceFiles {
|
||||
if !clone.Markup.Goldmark.DuplicateResourceFiles {
|
||||
if clone.Markup.Goldmark.RenderHooks.Link.EnableDefault == nil {
|
||||
clone.Markup.Goldmark.RenderHooks.Link.EnableDefault = types.NewBool(true)
|
||||
@@ -933,7 +957,7 @@ func fromLoadConfigResult(fs afero.Fs, logger loggers.Logger, res config.LoadCon
|
||||
Base: all,
|
||||
LanguageConfigMap: langConfigMap,
|
||||
LoadingInfo: res,
|
||||
IsMultihost: isMultiHost,
|
||||
IsMultihost: isMultihost,
|
||||
}
|
||||
|
||||
return cm, nil
|
||||
@@ -966,7 +990,7 @@ func decodeConfigFromParams(fs afero.Fs, logger loggers.Logger, bcfg config.Base
|
||||
})
|
||||
|
||||
for _, v := range decoderSetups {
|
||||
p := decodeConfig{p: p, c: target, fs: fs, logger: logger, bcfg: bcfg}
|
||||
p := decodeConfig{p: p, c: target, fs: fs, bcfg: bcfg}
|
||||
if err := v.decode(v, p); err != nil {
|
||||
return fmt.Errorf("failed to decode %q: %w", v.key, err)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/cache/filecache"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
@@ -26,11 +25,13 @@ import (
|
||||
"github.com/gohugoio/hugo/config/security"
|
||||
"github.com/gohugoio/hugo/config/services"
|
||||
"github.com/gohugoio/hugo/deploy/deployconfig"
|
||||
"github.com/gohugoio/hugo/hugolib/segments"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
"github.com/gohugoio/hugo/markup/markup_config"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/gohugoio/hugo/minifiers"
|
||||
"github.com/gohugoio/hugo/modules"
|
||||
|
||||
"github.com/gohugoio/hugo/navigation"
|
||||
"github.com/gohugoio/hugo/output"
|
||||
"github.com/gohugoio/hugo/related"
|
||||
@@ -43,11 +44,10 @@ import (
|
||||
)
|
||||
|
||||
type decodeConfig struct {
|
||||
p config.Provider
|
||||
c *Config
|
||||
fs afero.Fs
|
||||
logger loggers.Logger
|
||||
bcfg config.BaseConfig
|
||||
p config.Provider
|
||||
c *Config
|
||||
fs afero.Fs
|
||||
bcfg config.BaseConfig
|
||||
}
|
||||
|
||||
type decodeWeight struct {
|
||||
@@ -122,6 +122,14 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
return err
|
||||
},
|
||||
},
|
||||
"segments": {
|
||||
key: "segments",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.Segments, err = segments.DecodeSegments(p.p.GetStringMap(d.key))
|
||||
return err
|
||||
},
|
||||
},
|
||||
"server": {
|
||||
key: "server",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
@@ -293,7 +301,7 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
key: "cascade",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.Cascade, err = page.DecodeCascadeConfig(p.logger, p.p.Get(d.key))
|
||||
p.c.Cascade, err = page.DecodeCascadeConfig(nil, p.p.Get(d.key))
|
||||
return err
|
||||
},
|
||||
},
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/common/urls"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
)
|
||||
|
||||
@@ -42,7 +43,7 @@ func (c ConfigLanguage) LanguagesDefaultFirst() langs.Languages {
|
||||
return c.m.LanguagesDefaultFirst
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) PathParser() paths.PathParser {
|
||||
func (c ConfigLanguage) PathParser() *paths.PathParser {
|
||||
return c.m.ContentPathParser
|
||||
}
|
||||
|
||||
@@ -51,7 +52,7 @@ func (c ConfigLanguage) LanguagePrefix() string {
|
||||
return c.Language().Lang
|
||||
}
|
||||
|
||||
if !c.IsMultiLingual() || c.DefaultContentLanguage() == c.Language().Lang {
|
||||
if !c.IsMultilingual() || c.DefaultContentLanguage() == c.Language().Lang {
|
||||
return ""
|
||||
}
|
||||
return c.Language().Lang
|
||||
@@ -77,7 +78,7 @@ func (c ConfigLanguage) FastRenderMode() bool {
|
||||
return c.config.Internal.FastRenderMode
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) IsMultiLingual() bool {
|
||||
func (c ConfigLanguage) IsMultilingual() bool {
|
||||
return len(c.m.Languages) > 1
|
||||
}
|
||||
|
||||
@@ -133,6 +134,13 @@ func (c ConfigLanguage) Watching() bool {
|
||||
return c.m.Base.Internal.Watch
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) NewIdentityManager(name string) identity.Manager {
|
||||
if !c.Watching() {
|
||||
return identity.NopManager
|
||||
}
|
||||
return identity.NewManager(name)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
@@ -189,6 +189,7 @@ func (l configLoader) applyDefaultConfig() error {
|
||||
"menus": maps.Params{},
|
||||
"disableLiveReload": false,
|
||||
"pluralizeListTitles": true,
|
||||
"capitalizeListTitles": true,
|
||||
"forceSyncStatic": false,
|
||||
"footnoteAnchorPrefix": "",
|
||||
"footnoteReturnLinkContents": "",
|
||||
|
||||
@@ -215,6 +215,8 @@ type SitemapConfig struct {
|
||||
Priority float64
|
||||
// The sitemap filename.
|
||||
Filename string
|
||||
// Whether to disable page inclusion.
|
||||
Disable bool
|
||||
}
|
||||
|
||||
func DecodeSitemap(prototype SitemapConfig, input map[string]any) (SitemapConfig, error) {
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/common/urls"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
)
|
||||
|
||||
@@ -31,10 +32,10 @@ type AllProvider interface {
|
||||
LanguagePrefix() string
|
||||
BaseURL() urls.BaseURL
|
||||
BaseURLLiveReload() urls.BaseURL
|
||||
PathParser() paths.PathParser
|
||||
PathParser() *paths.PathParser
|
||||
Environment() string
|
||||
IsMultihost() bool
|
||||
IsMultiLingual() bool
|
||||
IsMultilingual() bool
|
||||
NoBuildLock() bool
|
||||
BaseConfig() BaseConfig
|
||||
Dirs() CommonDirs
|
||||
@@ -57,6 +58,7 @@ type AllProvider interface {
|
||||
BuildDrafts() bool
|
||||
Running() bool
|
||||
Watching() bool
|
||||
NewIdentityManager(name string) identity.Manager
|
||||
FastRenderMode() bool
|
||||
PrintUnusedTemplates() bool
|
||||
EnableMissingTranslationPlaceholders() bool
|
||||
|
||||
@@ -44,15 +44,9 @@ type Disqus struct {
|
||||
type GoogleAnalytics struct {
|
||||
Service `mapstructure:",squash"`
|
||||
|
||||
// Enabling this will disable the use of Cookies and use Session Storage to Store the GA Client ID.
|
||||
UseSessionStorage bool
|
||||
|
||||
// Enabling this will make the GA templates respect the
|
||||
// "Do Not Track" HTTP header. See https://www.paulfurley.com/google-analytics-dnt/.
|
||||
RespectDoNotTrack bool
|
||||
|
||||
// Enabling this will make it so the users' IP addresses are anonymized within Google Analytics.
|
||||
AnonymizeIP bool
|
||||
}
|
||||
|
||||
// Instagram holds the privacy configuration settings related to the Instagram shortcode.
|
||||
|
||||
@@ -33,8 +33,6 @@ disable = true
|
||||
[privacy.googleAnalytics]
|
||||
disable = true
|
||||
respectDoNotTrack = true
|
||||
anonymizeIP = true
|
||||
useSessionStorage = true
|
||||
[privacy.instagram]
|
||||
disable = true
|
||||
simple = true
|
||||
@@ -60,8 +58,7 @@ simple = true
|
||||
|
||||
got := []bool{
|
||||
pc.Disqus.Disable, pc.GoogleAnalytics.Disable,
|
||||
pc.GoogleAnalytics.RespectDoNotTrack, pc.GoogleAnalytics.AnonymizeIP,
|
||||
pc.GoogleAnalytics.UseSessionStorage, pc.Instagram.Disable,
|
||||
pc.GoogleAnalytics.RespectDoNotTrack, pc.Instagram.Disable,
|
||||
pc.Instagram.Simple, pc.Twitter.Disable, pc.Twitter.EnableDNT,
|
||||
pc.Twitter.Simple, pc.Vimeo.Disable, pc.Vimeo.EnableDNT, pc.Vimeo.Simple,
|
||||
pc.YouTube.PrivacyEnhanced, pc.YouTube.Disable,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
For a given taxonomy, renders a list of terms assigned to the page.
|
||||
|
||||
@context {page} page The current page.
|
||||
@context {string} taxonomy The taxonony.
|
||||
@context {string} taxonomy The taxonomy.
|
||||
|
||||
@example: {{ partial "terms.html" (dict "taxonomy" "tags" "page" .) }}
|
||||
*/}}
|
||||
|
||||
Vendored
+1
-1
@@ -361,7 +361,7 @@ type BuildState struct {
|
||||
|
||||
mu sync.Mutex // protects state below.
|
||||
|
||||
// A set of ilenames in /public that
|
||||
// A set of filenames in /public that
|
||||
// contains a post-processing prefix.
|
||||
filenamesWithPostPrefix map[string]bool
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ hugo [flags]
|
||||
--printPathWarnings print warnings on duplicate target paths etc.
|
||||
--printUnusedTemplates print warnings on unused templates.
|
||||
--quiet build in quiet mode
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
--renderToMemory render to memory (mostly useful when running the server)
|
||||
-s, --source string filesystem path to read files relative from
|
||||
--templateMetrics display metrics about template executions
|
||||
|
||||
@@ -18,13 +18,14 @@ hugo config [command] [flags]
|
||||
### Options
|
||||
|
||||
```
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
--format string preferred file format (toml, yaml or json) (default "toml")
|
||||
-h, --help help for config
|
||||
--lang string the language to display config for. Defaults to the first language defined.
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
--format string preferred file format (toml, yaml or json) (default "toml")
|
||||
-h, --help help for config
|
||||
--lang string the language to display config for. Defaults to the first language defined.
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -14,11 +14,12 @@ hugo config mounts [flags] [args]
|
||||
### Options
|
||||
|
||||
```
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for mounts
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for mounts
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -18,13 +18,14 @@ hugo mod clean [flags] [args]
|
||||
### Options
|
||||
|
||||
```
|
||||
--all clean entire module cache
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for clean
|
||||
--pattern string pattern matching module paths to clean (all if not set), e.g. "**hugo*"
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
--all clean entire module cache
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for clean
|
||||
--pattern string pattern matching module paths to clean (all if not set), e.g. "**hugo*"
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -20,12 +20,13 @@ hugo mod graph [flags] [args]
|
||||
### Options
|
||||
|
||||
```
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
--clean delete module cache for dependencies that fail verification
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for graph
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
--clean delete module cache for dependencies that fail verification
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for graph
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -25,11 +25,12 @@ hugo mod init [flags] [args]
|
||||
### Options
|
||||
|
||||
```
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for init
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for init
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -28,11 +28,12 @@ hugo mod npm pack [flags] [args]
|
||||
### Options
|
||||
|
||||
```
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for pack
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for pack
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -14,11 +14,12 @@ hugo mod tidy [flags] [args]
|
||||
### Options
|
||||
|
||||
```
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for tidy
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for tidy
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -20,11 +20,12 @@ hugo mod vendor [flags] [args]
|
||||
### Options
|
||||
|
||||
```
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for vendor
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for vendor
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -18,12 +18,13 @@ hugo mod verify [flags] [args]
|
||||
### Options
|
||||
|
||||
```
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
--clean delete module cache for dependencies that fail verification
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for verify
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
--clean delete module cache for dependencies that fail verification
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
-h, --help help for verify
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -25,14 +25,15 @@ hugo new content [path] [flags]
|
||||
### Options
|
||||
|
||||
```
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
--editor string edit new content with this editor, if provided
|
||||
-f, --force overwrite file if it already exists
|
||||
-h, --help help for content
|
||||
-k, --kind string content type to create
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--cacheDir string filesystem path to cache directory
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
--editor string edit new content with this editor, if provided
|
||||
-f, --force overwrite file if it already exists
|
||||
-h, --help help for content
|
||||
-k, --kind string content type to create
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -12,9 +12,9 @@ A high performance webserver
|
||||
Hugo provides its own webserver which builds and serves the site.
|
||||
While hugo server is high performance, it is a webserver with limited options.
|
||||
|
||||
'hugo server' will by default write and server files from disk, but you can
|
||||
render to memory by using the '--renderToMemory' flag. This can be faster
|
||||
in some cases, but it will consume more memory.
|
||||
The `hugo server` command will by default write and serve files from disk, but
|
||||
you can render to memory by using the `--renderToMemory` flag. This can be
|
||||
faster in some cases, but it will consume more memory.
|
||||
|
||||
By default hugo will also watch your files for any changes you make and
|
||||
automatically rebuild the site. It will then live reload any open browser pages
|
||||
@@ -28,49 +28,50 @@ hugo server [command] [flags]
|
||||
### Options
|
||||
|
||||
```
|
||||
--appendPort append port to baseURL (default true)
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--bind string interface to which the server will bind (default "127.0.0.1")
|
||||
-D, --buildDrafts include content marked as draft
|
||||
-E, --buildExpired include expired content
|
||||
-F, --buildFuture include content with publishdate in the future
|
||||
--cacheDir string filesystem path to cache directory
|
||||
--cleanDestinationDir remove files from destination not found in static directories
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
--disableBrowserError do not show build errors in the browser
|
||||
--disableFastRender enables full re-renders on changes
|
||||
--disableKinds strings disable different kind of pages (home, RSS etc.)
|
||||
--disableLiveReload watch without enabling live browser reload on rebuild
|
||||
--enableGitInfo add Git revision, date, author, and CODEOWNERS info to the pages
|
||||
--forceSyncStatic copy all files when static is changed.
|
||||
--gc enable to run some cleanup tasks (remove unused cache files) after the build
|
||||
-h, --help help for server
|
||||
--ignoreCache ignores the cache directory
|
||||
-l, --layoutDir string filesystem path to layout directory
|
||||
--liveReloadPort int port for live reloading (i.e. 443 in HTTPS proxy situations) (default -1)
|
||||
--minify minify any supported output format (HTML, XML etc.)
|
||||
--navigateToChanged navigate to changed content file on live browser reload
|
||||
--noBuildLock don't create .hugo_build.lock file
|
||||
--noChmod don't sync permission mode of files
|
||||
--noHTTPCache prevent HTTP caching
|
||||
--noTimes don't sync modification time of files
|
||||
--panicOnWarning panic on first WARNING log
|
||||
--poll string set this to a poll interval, e.g --poll 700ms, to use a poll based approach to watch for file system changes
|
||||
-p, --port int port on which the server will listen (default 1313)
|
||||
--pprof enable the pprof server (port 8080)
|
||||
--printI18nWarnings print missing translations
|
||||
--printMemoryUsage print memory usage to screen at intervals
|
||||
--printPathWarnings print warnings on duplicate target paths etc.
|
||||
--printUnusedTemplates print warnings on unused templates.
|
||||
--renderStaticToDisk serve static files from disk and dynamic files from memory
|
||||
--templateMetrics display metrics about template executions
|
||||
--templateMetricsHints calculate some improvement hints when combined with --templateMetrics
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
--tlsAuto generate and use locally-trusted certificates.
|
||||
--tlsCertFile string path to TLS certificate file
|
||||
--tlsKeyFile string path to TLS key file
|
||||
--trace file write trace to file (not useful in general)
|
||||
-w, --watch watch filesystem for changes and recreate as needed (default true)
|
||||
--appendPort append port to baseURL (default true)
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
--bind string interface to which the server will bind (default "127.0.0.1")
|
||||
-D, --buildDrafts include content marked as draft
|
||||
-E, --buildExpired include expired content
|
||||
-F, --buildFuture include content with publishdate in the future
|
||||
--cacheDir string filesystem path to cache directory
|
||||
--cleanDestinationDir remove files from destination not found in static directories
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
--disableBrowserError do not show build errors in the browser
|
||||
--disableFastRender enables full re-renders on changes
|
||||
--disableKinds strings disable different kind of pages (home, RSS etc.)
|
||||
--disableLiveReload watch without enabling live browser reload on rebuild
|
||||
--enableGitInfo add Git revision, date, author, and CODEOWNERS info to the pages
|
||||
--forceSyncStatic copy all files when static is changed.
|
||||
--gc enable to run some cleanup tasks (remove unused cache files) after the build
|
||||
-h, --help help for server
|
||||
--ignoreCache ignores the cache directory
|
||||
-l, --layoutDir string filesystem path to layout directory
|
||||
--liveReloadPort int port for live reloading (i.e. 443 in HTTPS proxy situations) (default -1)
|
||||
--minify minify any supported output format (HTML, XML etc.)
|
||||
--navigateToChanged navigate to changed content file on live browser reload
|
||||
--noBuildLock don't create .hugo_build.lock file
|
||||
--noChmod don't sync permission mode of files
|
||||
--noHTTPCache prevent HTTP caching
|
||||
--noTimes don't sync modification time of files
|
||||
--panicOnWarning panic on first WARNING log
|
||||
--poll string set this to a poll interval, e.g --poll 700ms, to use a poll based approach to watch for file system changes
|
||||
-p, --port int port on which the server will listen (default 1313)
|
||||
--pprof enable the pprof server (port 8080)
|
||||
--printI18nWarnings print missing translations
|
||||
--printMemoryUsage print memory usage to screen at intervals
|
||||
--printPathWarnings print warnings on duplicate target paths etc.
|
||||
--printUnusedTemplates print warnings on unused templates.
|
||||
--renderSegments strings named segments to render (configured in the segments config)
|
||||
--renderStaticToDisk serve static files from disk and dynamic files from memory
|
||||
--templateMetrics display metrics about template executions
|
||||
--templateMetricsHints calculate some improvement hints when combined with --templateMetrics
|
||||
-t, --theme strings themes to use (located in /themes/THEMENAME/)
|
||||
--tlsAuto generate and use locally-trusted certificates.
|
||||
--tlsCertFile string path to TLS certificate file
|
||||
--tlsKeyFile string path to TLS key file
|
||||
--trace file write trace to file (not useful in general)
|
||||
-w, --watch watch filesystem for changes and recreate as needed (default true)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -50,7 +50,7 @@ render
|
||||
: Always render the page to disk. This is the default value.
|
||||
|
||||
- `link`
|
||||
: Do not render the page to disk, but include it in all page collections.
|
||||
: Do not render the page to disk, but assign `Permalink` and `RelPermalink` values.
|
||||
|
||||
- `never`
|
||||
: Never render the page to disk, and exclude it from all page collections.
|
||||
@@ -245,7 +245,7 @@ content/
|
||||
|
||||
Set the build options in front matter:
|
||||
|
||||
{{< code-toggle file=content/books/_index.md >}}
|
||||
{{< code-toggle file=content/books/_index.md fm=true >}}
|
||||
title = 'Books'
|
||||
[_build]
|
||||
render = 'never'
|
||||
|
||||
@@ -8,7 +8,7 @@ action:
|
||||
- functions/cast/ToFloat
|
||||
- functions/cast/ToString
|
||||
returnType: int
|
||||
signatures: [cast/ToInt INPUT]
|
||||
signatures: [cast.ToInt INPUT]
|
||||
aliases: [/functions/int]
|
||||
---
|
||||
|
||||
@@ -49,5 +49,5 @@ With a hexadecimal (base 16) input:
|
||||
{{% note %}}
|
||||
Values with a leading zero are octal (base 8). When casting a string representation of a decimal (base 10) number, remove leading zeros:
|
||||
|
||||
`{{ strings/TrimLeft "0" "0011" | int }} → 11`
|
||||
`{{ strings.TrimLeft "0" "0011" | int }} → 11`
|
||||
{{% /note %}}
|
||||
|
||||
@@ -15,6 +15,8 @@ action:
|
||||
aliases: [/functions/crypto.fnv32a]
|
||||
---
|
||||
|
||||
{{< new-in 0.98.0 >}}
|
||||
|
||||
This function calculates the 32-bit [FNV1a hash](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash) of a given string according to the [specification](https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-12):
|
||||
|
||||
```go-html-template
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
---
|
||||
title: images.Dither
|
||||
description: Returns an image filter that dithers an image.
|
||||
categories: []
|
||||
keywords: []
|
||||
action:
|
||||
aliases: []
|
||||
related:
|
||||
- functions/images/Filter
|
||||
- functions/images/Process
|
||||
- methods/resource/Colors
|
||||
- methods/resource/Filter
|
||||
returnType: images.filter
|
||||
signatures: ['images.Dither [OPTIONS]']
|
||||
toc: true
|
||||
---
|
||||
|
||||
## Options
|
||||
|
||||
colors
|
||||
: (`string array`) A slice of two or more colors that make up the dithering palette, each expressed as an RGB or RGBA [hexadecimal] value, with or without a leading hash mark. The default values are opaque black (`000000ff`) and opaque white (`ffffffff`).
|
||||
|
||||
[hexadecimal]: https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color
|
||||
|
||||
method
|
||||
: (`string`) The dithering method. See the [dithering methods](#dithering-methods) section below for a list of the available methods. Default is `FloydSteinberg`.
|
||||
|
||||
serpentine
|
||||
: (`bool`) Applicable to error diffusion dithering methods, serpentine controls whether the error diffusion matrix is applied in a serpentine manner, meaning that it goes right-to-left every other line. This greatly reduces line-type artifacts. Default is `true`.
|
||||
|
||||
strength
|
||||
: (`float`) The strength at which to apply the dithering matrix, typically a value in the range [0, 1]. A value of `1.0` applies the dithering matrix at 100% strength (no modifification of the dither matrix). The `strength` is inversely proportional to contrast; reducing the strength increases the contrast. Setting `strength` to a value such as `0.8` can be useful to reduce noise in the dithered image. Default is `1.0`.
|
||||
|
||||
## Usage
|
||||
|
||||
Create the options map:
|
||||
|
||||
```go-html-template
|
||||
{{ $opts := dict
|
||||
"colors" (slice "222222" "808080" "dddddd")
|
||||
"method" "ClusteredDot4x4"
|
||||
"strength" 0.85
|
||||
}}
|
||||
```
|
||||
|
||||
Create the filter:
|
||||
|
||||
```go-html-template
|
||||
{{ $filter := images.Dither $opts }}
|
||||
```
|
||||
|
||||
Or create the filter using the default settings:
|
||||
|
||||
```go-html-template
|
||||
{{ $filter := images.Dither }}
|
||||
```
|
||||
|
||||
{{% include "functions/images/_common/apply-image-filter.md" %}}
|
||||
|
||||
## Dithering methods
|
||||
|
||||
See the [Go documentation] for descriptions of each of the dithering methods below.
|
||||
|
||||
[Go documentation]: https://pkg.go.dev/github.com/makeworld-the-better-one/dither/v2#pkg-variables
|
||||
|
||||
Error diffusion dithering methods:
|
||||
|
||||
- Atkinson
|
||||
- Burkes
|
||||
- FalseFloydSteinberg
|
||||
- FloydSteinberg
|
||||
- JarvisJudiceNinke
|
||||
- Sierra
|
||||
- Sierra2
|
||||
- Sierra2_4A
|
||||
- Sierra3
|
||||
- SierraLite
|
||||
- Simple2D
|
||||
- StevenPigeon
|
||||
- Stucki
|
||||
- TwoRowSierra
|
||||
|
||||
Ordered dithering methods:
|
||||
|
||||
- ClusteredDot4x4
|
||||
- ClusteredDot6x6
|
||||
- ClusteredDot6x6_2
|
||||
- ClusteredDot6x6_3
|
||||
- ClusteredDot8x8
|
||||
- ClusteredDotDiagonal16x16
|
||||
- ClusteredDotDiagonal6x6
|
||||
- ClusteredDotDiagonal8x8
|
||||
- ClusteredDotDiagonal8x8_2
|
||||
- ClusteredDotDiagonal8x8_3
|
||||
- ClusteredDotHorizontalLine
|
||||
- ClusteredDotSpiral5x5
|
||||
- ClusteredDotVerticalLine
|
||||
- Horizontal3x5
|
||||
- Vertical5x3
|
||||
|
||||
## Example
|
||||
|
||||
This example uses the default dithering options.
|
||||
|
||||
{{< img
|
||||
src="images/examples/zion-national-park.jpg"
|
||||
alt="Zion National Park"
|
||||
filter="Dither"
|
||||
filterArgs=""
|
||||
example=true
|
||||
>}}
|
||||
|
||||
## Recommendations
|
||||
|
||||
Regardless of dithering method, do both of the following to obtain the best results:
|
||||
|
||||
1. Scale the image _before_ dithering
|
||||
2. Output the image to a lossless format such as GIF or PNG
|
||||
|
||||
The example below does both of these, and it sets the dithering palette to the three most dominant colors in the image.
|
||||
|
||||
|
||||
```go-html-template
|
||||
{{ with resources.Get "original.jpg" }}
|
||||
{{ $opts := dict
|
||||
"method" "ClusteredDotSpiral5x5"
|
||||
"colors" (first 3 .Colors)
|
||||
}}
|
||||
{{ $filters := slice
|
||||
(images.Process "resize 800x")
|
||||
(images.Dither $opts)
|
||||
(images.Process "png")
|
||||
}}
|
||||
{{ with . | images.Filter $filters }}
|
||||
<img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}" alt="">
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
For best results, if the dithering palette is grayscale, convert the image to grayscale before dithering.
|
||||
|
||||
```go-html-template
|
||||
{{ $opts := dict "colors" (slice "222" "808080" "ddd") }}
|
||||
{{ $filters := slice
|
||||
(images.Process "resize 800x")
|
||||
(images.Grayscale)
|
||||
(images.Dither $opts)
|
||||
(images.Process "png")
|
||||
}}
|
||||
{{ with images.Filter $filters . }}
|
||||
<img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}" alt="">
|
||||
{{ end }}
|
||||
```
|
||||
|
||||
The example above:
|
||||
|
||||
1. Resizes the image to be 800 px wide
|
||||
2. Converts the image to grayscale
|
||||
3. Dithers the image using the default (`FloydSteinberg`) dithering method with a grayscale palette
|
||||
4. Converts the image to the PNG format
|
||||
@@ -111,6 +111,29 @@ format
|
||||
sourceMap
|
||||
: (`string`) Whether to generate `inline` or `external` source maps from esbuild. External source maps will be written to the target with the output file name + ".map". Input source maps can be read from js.Build and node modules and combined into the output source maps. By default, source maps are not created.
|
||||
|
||||
JSX {{< new-in 0.124.0 >}}
|
||||
: (`string`) How to handle/transform JSX syntax. One of: `transform`, `preserve`, `automatic`. Default is `transform`. Notably, the `automatic` transform was introduced in React 17+ and will cause the necessary JSX helper functions to be imported automatically. See https://esbuild.github.io/api/#jsx
|
||||
|
||||
JSXImportSource {{< new-in 0.124.0 >}}
|
||||
: (`string`) Which library to use to automatically import its JSX helper functions from. This only works if `JSX` is set to `automatic`. The specified library needs to be installed through NPM and expose certain exports. See https://esbuild.github.io/api/#jsx-import-source
|
||||
|
||||
The combination of `JSX` and `JSXImportSource` is helpful if you want to use a non-React JSX library like Preact, e.g.:
|
||||
|
||||
```go-html-template
|
||||
{{ $js := resources.Get "js/main.jsx" | js.Build (dict "JSX" "automatic" "JSXImportSource" "preact") }}
|
||||
```
|
||||
|
||||
With the above, you can use Preact components and JSX without having to manually import `h` and `Fragment` every time:
|
||||
|
||||
```jsx
|
||||
import { render } from 'preact';
|
||||
|
||||
const App = () => <>Hello world!</>;
|
||||
|
||||
const container = document.getElementById('app');
|
||||
if (container) render(<App />, container);
|
||||
```
|
||||
|
||||
### Import JS code from /assets
|
||||
|
||||
`js.Build` has full support for the virtual union file system in [Hugo Modules](/hugo-modules/). You can see some simple examples in this [test project](https://github.com/gohugoio/hugoTestProjectJSModImports), but in short this means that you can do this:
|
||||
|
||||
@@ -8,17 +8,18 @@ action:
|
||||
related: []
|
||||
returnType: string
|
||||
signatures: ['lang.Translate KEY [CONTEXT]']
|
||||
toc: true
|
||||
aliases: [/functions/i18n]
|
||||
---
|
||||
|
||||
The `lang.Translate` function returns the value associated with given key as defined in the translation table for the current language.
|
||||
The `lang.Translate` function returns the value associated with given key as defined in the translation table for the current language.
|
||||
|
||||
If the key is not found in the translation table for the current language, the `lang.Translate` function falls back to the translation table for the [`defaultContentLanguage`].
|
||||
|
||||
If the key is not found in the translation table for the `defaultContentLanguage`, the `lang.Translate` function returns an empty string.
|
||||
If the key is not found in the translation table for the current language, the `lang.Translate` function falls back to the translation table for the [`defaultContentLanguage`].
|
||||
|
||||
[`defaultContentLanguage`]: /getting-started/configuration/#defaultcontentlanguage
|
||||
|
||||
If the key is not found in the translation table for the `defaultContentLanguage`, the `lang.Translate` function returns an empty string.
|
||||
|
||||
{{% note %}}
|
||||
To list missing and fallback translations, use the `--printI18nWarnings` flag when building your site.
|
||||
|
||||
@@ -28,6 +29,31 @@ To render placeholders for missing and fallback translations, set
|
||||
[`enableMissingTranslationPlaceholders`]: /getting-started/configuration/#enablemissingtranslationplaceholders
|
||||
{{% /note %}}
|
||||
|
||||
## Translation tables
|
||||
|
||||
Create translation tables in the i18n directory, naming each file according to [RFC 5646]. Translation tables may be JSON, TOML, or YAML. For example:
|
||||
|
||||
```text
|
||||
i18n/en.toml
|
||||
i18n/en-US.toml
|
||||
```
|
||||
|
||||
The base name must match the language key as defined in your site configuration.
|
||||
|
||||
Artificial languages with private use subtags as defined in [RFC 5646 § 2.2.7] are also supported. You may omit the `art-x-` prefix for brevity. For example:
|
||||
|
||||
```text
|
||||
i18n/art-x-hugolang.toml
|
||||
i18n/hugolang.toml
|
||||
```
|
||||
|
||||
Private use subtags must not exceed 8 alphanumeric characters.
|
||||
|
||||
[RFC 5646]: https://datatracker.ietf.org/doc/html/rfc5646
|
||||
[RFC 5646 § 2.2.7]: https://datatracker.ietf.org/doc/html/rfc5646#section-2.2.7
|
||||
|
||||
## Simple translations
|
||||
|
||||
Let's say your multilingual site supports two languages, English and Polish. Create a translation table for each language in the `i18n` directory.
|
||||
|
||||
```text
|
||||
@@ -36,10 +62,47 @@ i18n/
|
||||
└── pl.toml
|
||||
```
|
||||
|
||||
The translation tables can contain both:
|
||||
The English translation table:
|
||||
|
||||
- Simple translations
|
||||
- Translations with pluralization
|
||||
{{< code-toggle file=i18n/en >}}
|
||||
privacy = 'privacy'
|
||||
security = 'security'
|
||||
{{< /code-toggle >}}
|
||||
|
||||
The Polish translation table:
|
||||
|
||||
{{< code-toggle file=i18n/pl >}}
|
||||
privacy = 'prywatność'
|
||||
security = 'bezpieczeństwo'
|
||||
{{< /code-toggle >}}
|
||||
|
||||
{{% note %}}
|
||||
The examples below use the `T` alias for brevity.
|
||||
{{% /note %}}
|
||||
|
||||
When viewing the English language site:
|
||||
|
||||
```go-html-template
|
||||
{{ T "privacy" }} → privacy
|
||||
{{ T "security" }} → security
|
||||
````
|
||||
|
||||
When viewing the Polish language site:
|
||||
|
||||
```go-html-template
|
||||
{{ T "privacy" }} → prywatność
|
||||
{{ T "security" }} → bezpieczeństwo
|
||||
```
|
||||
|
||||
## Translations with pluralization
|
||||
|
||||
Let's say your multilingual site supports two languages, English and Polish. Create a translation table for each language in the `i18n` directory.
|
||||
|
||||
```text
|
||||
i18n/
|
||||
├── en.toml
|
||||
└── pl.toml
|
||||
```
|
||||
|
||||
The Unicode [CLDR Plural Rules chart] describes the pluralization categories for each language.
|
||||
|
||||
@@ -48,9 +111,6 @@ The Unicode [CLDR Plural Rules chart] describes the pluralization categories for
|
||||
The English translation table:
|
||||
|
||||
{{< code-toggle file=i18n/en >}}
|
||||
privacy = 'privacy'
|
||||
security = 'security'
|
||||
|
||||
[day]
|
||||
one = 'day'
|
||||
other = 'days'
|
||||
@@ -63,9 +123,6 @@ other = '{{ . }} days'
|
||||
The Polish translation table:
|
||||
|
||||
{{< code-toggle file=i18n/pl >}}
|
||||
privacy = 'prywatność'
|
||||
security = 'bezpieczeństwo'
|
||||
|
||||
[day]
|
||||
one = 'miesiąc'
|
||||
few = 'miesiące'
|
||||
@@ -86,9 +143,6 @@ The examples below use the `T` alias for brevity.
|
||||
When viewing the English language site:
|
||||
|
||||
```go-html-template
|
||||
{{ T "privacy" }} → privacy
|
||||
{{ T "security" }} → security
|
||||
|
||||
{{ T "day" 0 }} → days
|
||||
{{ T "day" 1 }} → day
|
||||
{{ T "day" 2 }} → days
|
||||
@@ -103,9 +157,6 @@ When viewing the English language site:
|
||||
When viewing the Polish language site:
|
||||
|
||||
```go-html-template
|
||||
{{ T "privacy" }} → prywatność
|
||||
{{ T "security" }} → bezpieczeństwo
|
||||
|
||||
{{ T "day" 0 }} → miesięcy
|
||||
{{ T "day" 1 }} → miesiąc
|
||||
{{ T "day" 2 }} → miesiące
|
||||
@@ -133,3 +184,72 @@ Template code:
|
||||
{{ T "age" (dict "name" "Will" "count" 1) }} → Will is 1 year old.
|
||||
{{ T "age" (dict "name" "John" "count" 3) }} → John is 3 years old.
|
||||
```
|
||||
|
||||
{{% note %}}
|
||||
Translation tables may contain both simple translations and translations with pluralization.
|
||||
{{% /note %}}
|
||||
|
||||
## Reserved keys
|
||||
|
||||
Hugo uses the [go-i18n] package to look up values in translation tables. This package reserves the following keys for internal use:
|
||||
|
||||
[go-i18n]: https://github.com/nicksnyder/go-i18n
|
||||
|
||||
id
|
||||
: (`string`) Uniquely identifies the message.
|
||||
|
||||
description
|
||||
: (`string`) Describes the message to give additional context to translators that may be relevant for translation.
|
||||
|
||||
hash
|
||||
: (`string`) Uniquely identifies the content of the message that this message was translated from.
|
||||
|
||||
leftdelim
|
||||
: (`string`) The left Go template delimiter.
|
||||
|
||||
rightdelim
|
||||
: (`string`) The right Go template delimiter.
|
||||
|
||||
zero
|
||||
: (`string`) The content of the message for the [CLDR] plural form "zero".
|
||||
|
||||
one
|
||||
: (`string`) The content of the message for the [CLDR] plural form "one".
|
||||
|
||||
two
|
||||
: (`string`) The content of the message for the [CLDR] plural form "two".
|
||||
|
||||
few
|
||||
: (`string`) The content of the message for the [CLDR] plural form "few".
|
||||
|
||||
many
|
||||
: (`string`) The content of the message for the [CLDR] plural form "many".
|
||||
|
||||
other
|
||||
: (`string`) The content of the message for the [CLDR] plural form "other".
|
||||
|
||||
[CLDR]: https://www.unicode.org/cldr/charts/43/supplemental/language_plural_rules.html
|
||||
|
||||
If you need to provide a translation for one of the reserved keys, you can prepend the word with an underscore. For example:
|
||||
|
||||
{{< code-toggle file=i18n/es >}}
|
||||
_description = 'descripción'
|
||||
_few = 'pocos'
|
||||
_many = 'muchos'
|
||||
_one = 'uno'
|
||||
_other = 'otro'
|
||||
_two = 'dos'
|
||||
_zero = 'cero'
|
||||
{{< /code-toggle >}}
|
||||
|
||||
Then in your templates:
|
||||
|
||||
```go-html-template
|
||||
{{ T "_description" }} → descripción
|
||||
{{ T "_few" }} → pocos
|
||||
{{ T "_many" }} → muchos
|
||||
{{ T "_one" }} → uno
|
||||
{{ T "_two" }} → dos
|
||||
{{ T "_zero" }} → cero
|
||||
{{ T "_other" }} → otro
|
||||
```
|
||||
|
||||
@@ -171,7 +171,7 @@ Override the cache key by setting a `key` in the options map. Use this approach
|
||||
```go-html-template
|
||||
{{ $url := "https://example.org/images/a.jpg" }}
|
||||
{{ $cacheKey := print $url (now.Format "2006-01-02") }}
|
||||
{{ $resource := resource.GetRemote $url (dict "key" $cacheKey) }}
|
||||
{{ $resource := resources.GetRemote $url (dict "key" $cacheKey) }}
|
||||
```
|
||||
|
||||
[configure file caches]: /getting-started/configuration/#configure-file-caches
|
||||
|
||||
@@ -95,6 +95,29 @@ format
|
||||
sourceMap
|
||||
: (`string`) Whether to generate `inline` or `external` source maps from esbuild. External source maps will be written to the target with the output file name + ".map". Input source maps can be read from js.Build and node modules and combined into the output source maps. By default, source maps are not created.
|
||||
|
||||
JSX {{< new-in 0.124.0 >}}
|
||||
: (`string`) How to handle/transform JSX syntax. One of: `transform`, `preserve`, `automatic`. Default is `transform`. Notably, the `automatic` transform was introduced in React 17+ and will cause the necessary JSX helper functions to be imported automatically. See https://esbuild.github.io/api/#jsx
|
||||
|
||||
JSXImportSource {{< new-in 0.124.0 >}}
|
||||
: (`string`) Which library to use to automatically import its JSX helper functions from. This only works if `JSX` is set to `automatic`. The specified library needs to be installed through NPM and expose certain exports. See https://esbuild.github.io/api/#jsx-import-source
|
||||
|
||||
The combination of `JSX` and `JSXImportSource` is helpful if you want to use a non-React JSX library like Preact, e.g.:
|
||||
|
||||
```go-html-template
|
||||
{{ $js := resources.Get "js/main.jsx" | js.Build (dict "JSX" "automatic" "JSXImportSource" "preact") }}
|
||||
```
|
||||
|
||||
With the above, you can use Preact components and JSX without having to manually import `h` and `Fragment` every time:
|
||||
|
||||
```jsx
|
||||
import { render } from 'preact';
|
||||
|
||||
const App = () => <>Hello world!</>;
|
||||
|
||||
const container = document.getElementById('app');
|
||||
if (container) render(<App />, container);
|
||||
```
|
||||
|
||||
### Import JS code from /assets
|
||||
|
||||
`js.Build` has full support for the virtual union file system in [Hugo Modules](/hugo-modules/). You can see some simple examples in this [test project](https://github.com/gohugoio/hugoTestProjectJSModImports), but in short this means that you can do this:
|
||||
|
||||
@@ -127,7 +127,7 @@ Achievements:
|
||||
You can use the following code to render the `Short Description` in your layout:
|
||||
|
||||
```go-html-template
|
||||
<div>Short Description of {{ .Site.Data.User0123.Name }}: <p>{{ index .Site.Data.User0123 "Short Description" | markdownify }}</p></div>
|
||||
<div>Short Description of {{ .Site.Data.user0123.Name }}: <p>{{ index .Site.Data.user0123 "Short Description" | markdownify }}</p></div>
|
||||
```
|
||||
|
||||
Note the use of the [`markdownify`] function. This will send the description through the Markdown rendering engine.
|
||||
|
||||
@@ -31,7 +31,7 @@ Setting `paginate` to a positive value will split the list pages for the homepag
|
||||
## List paginator pages
|
||||
|
||||
{{% note %}}
|
||||
`.Paginator` is provided to help you build a pager menu. This feature is currently only supported on homepage and list pages (i.e., taxonomies and section lists).
|
||||
Paginate a page collection in list templates for these page kinds: `home`, `section`, `taxonomy`, or `term`. You cannot paginate a page collection in a template for the `page` page kind.
|
||||
{{% /note %}}
|
||||
|
||||
There are two ways to configure and use a `.Paginator`:
|
||||
|
||||
@@ -18,8 +18,8 @@ aliases: [/templates/partial/,/layout/chrome/,/extras/analytics/]
|
||||
|
||||
Partial templates---like [single page templates][singletemps] and [list page templates][listtemps]---have a specific [lookup order]. However, partials are simpler in that Hugo will only check in two places:
|
||||
|
||||
1. `layouts/partials/*<PARTIALNAME>.html`
|
||||
2. `themes/<THEME>/layouts/partials/*<PARTIALNAME>.html`
|
||||
1. `layouts/partials/<PARTIALNAME>.html`
|
||||
2. `themes/<THEME>/layouts/partials/<PARTIALNAME>.html`
|
||||
|
||||
This allows a theme's end user to copy a partial's contents into a file of the same name for [further customization][customize].
|
||||
|
||||
|
||||
+51
-7
@@ -207,6 +207,10 @@ chroma:
|
||||
- Aliases:
|
||||
- dax
|
||||
Name: Dax
|
||||
- Aliases:
|
||||
- desktop
|
||||
- desktop_entry
|
||||
Name: Desktop file
|
||||
- Aliases:
|
||||
- diff
|
||||
- udiff
|
||||
@@ -443,6 +447,10 @@ chroma:
|
||||
- Aliases:
|
||||
- mason
|
||||
Name: Mason
|
||||
- Aliases:
|
||||
- materialize
|
||||
- mzsql
|
||||
Name: Materialize SQL dialect
|
||||
- Aliases:
|
||||
- mathematica
|
||||
- mma
|
||||
@@ -493,6 +501,9 @@ chroma:
|
||||
- Aliases:
|
||||
- natural
|
||||
Name: Natural
|
||||
- Aliases:
|
||||
- ndisasm
|
||||
Name: NDISASM
|
||||
- Aliases:
|
||||
- newspeak
|
||||
Name: Newspeak
|
||||
@@ -607,6 +618,9 @@ chroma:
|
||||
- Aliases:
|
||||
- prolog
|
||||
Name: Prolog
|
||||
- Aliases:
|
||||
- promela
|
||||
Name: Promela
|
||||
- Aliases:
|
||||
- promql
|
||||
Name: PromQL
|
||||
@@ -673,6 +687,9 @@ chroma:
|
||||
- Aliases:
|
||||
- registry
|
||||
Name: reg
|
||||
- Aliases:
|
||||
- rego
|
||||
Name: Rego
|
||||
- Aliases:
|
||||
- rst
|
||||
- rest
|
||||
@@ -682,6 +699,9 @@ chroma:
|
||||
- rexx
|
||||
- arexx
|
||||
Name: Rexx
|
||||
- Aliases:
|
||||
- spec
|
||||
Name: RPMSpec
|
||||
- Aliases:
|
||||
- rb
|
||||
- ruby
|
||||
@@ -946,6 +966,7 @@ config:
|
||||
dir: :cacheDir/modules
|
||||
maxAge: -1
|
||||
canonifyURLs: false
|
||||
capitalizeListTitles: true
|
||||
cascade: []
|
||||
cleanDestinationDir: false
|
||||
contentDir: content
|
||||
@@ -1536,10 +1557,8 @@ config:
|
||||
disqus:
|
||||
disable: false
|
||||
googleAnalytics:
|
||||
anonymizeIP: false
|
||||
disable: false
|
||||
respectDoNotTrack: false
|
||||
useSessionStorage: false
|
||||
instagram:
|
||||
disable: false
|
||||
simple: false
|
||||
@@ -1585,6 +1604,7 @@ config:
|
||||
toLower: false
|
||||
relativeURLs: false
|
||||
removePathAccents: false
|
||||
renderSegments: null
|
||||
resourceDir: resources
|
||||
sectionPagesMenu: ""
|
||||
security:
|
||||
@@ -1607,6 +1627,7 @@ config:
|
||||
- (?i)GET|POST
|
||||
urls:
|
||||
- .*
|
||||
segments: {}
|
||||
server:
|
||||
headers: null
|
||||
redirects:
|
||||
@@ -1628,6 +1649,7 @@ config:
|
||||
disableInlineCSS: false
|
||||
sitemap:
|
||||
changeFreq: ""
|
||||
disable: false
|
||||
filename: sitemap.xml
|
||||
priority: -1
|
||||
social: null
|
||||
@@ -1704,6 +1726,8 @@ config_helpers:
|
||||
_merge: none
|
||||
security:
|
||||
_merge: none
|
||||
segments:
|
||||
_merge: none
|
||||
server:
|
||||
_merge: none
|
||||
services:
|
||||
@@ -2506,7 +2530,7 @@ tpl:
|
||||
- shuffle
|
||||
Args:
|
||||
- l
|
||||
Description: Shuffle returns list l in a randomised order.
|
||||
Description: Shuffle returns list l in a randomized order.
|
||||
Examples: []
|
||||
Slice:
|
||||
Aliases:
|
||||
@@ -2774,8 +2798,8 @@ tpl:
|
||||
{{ $m.Set "Hugo" "Rocks!" }}
|
||||
{{ $m.Values | debug.Dump | safeHTML }}
|
||||
- |-
|
||||
map[string]interface {}{
|
||||
"Hugo": "Rocks!",
|
||||
{
|
||||
"Hugo": "Rocks!"
|
||||
}
|
||||
TestDeprecationErr:
|
||||
Aliases: null
|
||||
@@ -2898,7 +2922,7 @@ tpl:
|
||||
- format
|
||||
- args
|
||||
Description: Printf returns string representation of args formatted with the
|
||||
layouut in format.
|
||||
layout in format.
|
||||
Examples:
|
||||
- - '{{ printf "%s!" "works" }}'
|
||||
- works!
|
||||
@@ -2965,6 +2989,21 @@ tpl:
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
IsMultiHost:
|
||||
Aliases: null
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
IsMultihost:
|
||||
Aliases: null
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
IsMultilingual:
|
||||
Aliases: null
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
IsProduction:
|
||||
Aliases: null
|
||||
Args: null
|
||||
@@ -3020,6 +3059,11 @@ tpl:
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
Dither:
|
||||
Aliases: null
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
Filter:
|
||||
Aliases: null
|
||||
Args: null
|
||||
@@ -4537,5 +4581,5 @@ tpl:
|
||||
- urlize
|
||||
Args:
|
||||
- s
|
||||
Description: URLize returns the the strings s formatted as an URL.
|
||||
Description: URLize returns the strings s formatted as an URL.
|
||||
Examples: []
|
||||
|
||||
@@ -0,0 +1,381 @@
|
||||
{{- /*
|
||||
Renders the given image using the given filter, if any.
|
||||
|
||||
@param {string} src The path to the image which must be a remote, page, or global resource.
|
||||
@param {string} [filter] The filter to apply to the image (case-insensitive).
|
||||
@param {string} [filterArgs] A comma-delimited list of arguments to pass to the filter.
|
||||
@param {bool} [example=false] If true, renders a before/after example.
|
||||
@param {int} [exampleWidth=384] Image width, in pixels, when rendering a before/after example.
|
||||
|
||||
@returns {template.HTML}
|
||||
|
||||
@examples
|
||||
|
||||
{{< img src="zion-national-park.jpg" >}}
|
||||
|
||||
{{< img src="zion-national-park.jpg" alt="Zion National Park" >}}
|
||||
|
||||
{{< img
|
||||
src="zion-national-park.jpg"
|
||||
alt="Zion National Park"
|
||||
filter="grayscale"
|
||||
>}}
|
||||
|
||||
{{< img
|
||||
src="zion-national-park.jpg"
|
||||
alt="Zion National Park"
|
||||
filter="process"
|
||||
filterArgs="resize 400x webp"
|
||||
>}}
|
||||
|
||||
{{< img
|
||||
src="zion-national-park.jpg"
|
||||
alt="Zion National Park"
|
||||
filter="colorize"
|
||||
filterArgs="180,50,20"
|
||||
>}}
|
||||
|
||||
{{< img
|
||||
src="zion-national-park.jpg"
|
||||
alt="Zion National Park"
|
||||
filter="grayscale"
|
||||
example=true
|
||||
>}}
|
||||
|
||||
{{< img
|
||||
src="zion-national-park.jpg"
|
||||
alt="Zion National Park"
|
||||
filter="grayscale"
|
||||
example=true
|
||||
exampleWidth=400
|
||||
>}}
|
||||
|
||||
When using the text filter, provide the arguments in this order:
|
||||
|
||||
0. The text
|
||||
1. The horizontal offset, in pixels, relative to the left of the image (default 20)
|
||||
2. The vertical offset, in pixels, relative to the top of the image (default 20)
|
||||
3. The font size in pixels (default 64)
|
||||
4. The line height (default 1.2)
|
||||
5. The font color (default #ffffff)
|
||||
|
||||
{{< img
|
||||
src="images/examples/zion-national-park.jpg"
|
||||
alt="Zion National Park"
|
||||
filter="Text"
|
||||
filterArgs="Zion National Park,25,250,56"
|
||||
example=true
|
||||
>}}
|
||||
|
||||
When using the padding filter, provide all arguments in this order:
|
||||
|
||||
0. Padding top
|
||||
1. Padding right
|
||||
2. Padding bottom
|
||||
3. Padding right
|
||||
4. Canvas color
|
||||
|
||||
{{< img
|
||||
src="images/examples/zion-national-park.jpg"
|
||||
alt="Zion National Park"
|
||||
filter="Padding"
|
||||
filterArgs="20,50,20,50,#0705"
|
||||
example=true
|
||||
>}}
|
||||
|
||||
*/}}
|
||||
|
||||
{{- /* Initialize. */}}
|
||||
{{- $alt := "" }}
|
||||
{{- $src := "" }}
|
||||
{{- $filter := "" }}
|
||||
{{- $filterArgs := slice }}
|
||||
{{- $example := false }}
|
||||
{{- $exampleWidth := 384 }}
|
||||
|
||||
{{- /* Default values to use with the text filter. */}}
|
||||
{{ $textFilterOpts := dict
|
||||
"xOffset" 20
|
||||
"yOffset" 20
|
||||
"fontSize" 64
|
||||
"lineHeight" 1.2
|
||||
"fontColor" "#ffffff"
|
||||
"fontPath" "https://github.com/google/fonts/raw/main/ofl/lato/Lato-Regular.ttf"
|
||||
}}
|
||||
|
||||
{{- /* Get and validate parameters. */}}
|
||||
{{- with .Get "alt" }}
|
||||
{{- $alt = .}}
|
||||
{{- end }}
|
||||
|
||||
{{- with .Get "src" }}
|
||||
{{- $src = . }}
|
||||
{{- else }}
|
||||
{{- errorf "The %q shortcode requires a file parameter. See %s" .Name .Position }}
|
||||
{{- end }}
|
||||
|
||||
{{- with .Get "filter" }}
|
||||
{{- $filter = . | lower }}
|
||||
{{- end }}
|
||||
|
||||
{{- $validFilters := slice
|
||||
"autoorient" "brightness" "colorbalance" "colorize" "contrast" "dither"
|
||||
"gamma" "gaussianblur" "grayscale" "hue" "invert" "none" "opacity" "overlay"
|
||||
"padding" "pixelate" "process" "saturation" "sepia" "sigmoid" "text"
|
||||
"unsharpmask"
|
||||
}}
|
||||
|
||||
{{- with $filter }}
|
||||
{{- if not (in $validFilters .) }}
|
||||
{{- errorf "The filter passed to the %q shortcode is invalid. The filter must be one of %s. See %s" $.Name (delimit $validFilters ", " ", or ") $.Position }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- with .Get "filterArgs" }}
|
||||
{{- $filterArgs = split . "," }}
|
||||
{{- $filterArgs = apply $filterArgs "trim" "." " " }}
|
||||
{{- end }}
|
||||
|
||||
{{- if in (slice "false" false 0) (.Get "example") }}
|
||||
{{- $example = false }}
|
||||
{{- else if in (slice "true" true 1) (.Get "example")}}
|
||||
{{- $example = true }}
|
||||
{{- end }}
|
||||
|
||||
{{- with .Get "exampleWidth" }}
|
||||
{{- $exampleWidth = . | int }}
|
||||
{{- end }}
|
||||
|
||||
{{- /* Get image. */}}
|
||||
{{- $ctx := dict "page" .Page "src" $src "name" .Name "position" .Position }}
|
||||
{{- $i := partial "inline/get-resource.html" $ctx }}
|
||||
|
||||
{{- /* Resize if rendering before/after examples. */}}
|
||||
{{- if $example }}
|
||||
{{- $i = $i.Resize (printf "%dx" $exampleWidth) }}
|
||||
{{- end }}
|
||||
|
||||
{{- /* Create filter. */}}
|
||||
{{- $f := "" }}
|
||||
{{- $ctx := dict "filter" $filter "args" $filterArgs "name" .Name "position" .Position }}
|
||||
{{- if eq $filter "autoorient" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 0) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
{{- $f = images.AutoOrient }}
|
||||
{{- else if eq $filter "brightness" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 1) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
{{- $filterArgs = apply $filterArgs "float" "." }}
|
||||
{{- $ctx = merge $ctx (dict "argName" "percentage" "argValue" (index $filterArgs 0) "min" -100 "max" 100) }}
|
||||
{{- template "validate-arg-value" $ctx }}
|
||||
{{- $f = images.Brightness (index $filterArgs 0) }}
|
||||
{{- else if eq $filter "colorbalance" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 3) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
{{- $filterArgs = apply $filterArgs "float" "." }}
|
||||
{{- $ctx = merge $ctx (dict "argName" "percentage red" "argValue" (index $filterArgs 0) "min" -100 "max" 500) }}
|
||||
{{- template "validate-arg-value" $ctx }}
|
||||
{{- $ctx = merge $ctx (dict "argName" "percentage green" "argValue" (index $filterArgs 1) "min" -100 "max" 500) }}
|
||||
{{- template "validate-arg-value" $ctx }}
|
||||
{{- $ctx = merge $ctx (dict "argName" "percentage blue" "argValue" (index $filterArgs 2) "min" -100 "max" 500) }}
|
||||
{{- template "validate-arg-value" $ctx }}
|
||||
{{- $f = images.ColorBalance (index $filterArgs 0) (index $filterArgs 1) (index $filterArgs 2) }}
|
||||
{{- else if eq $filter "colorize" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 3) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
{{- $filterArgs = apply $filterArgs "float" "." }}
|
||||
{{- $ctx = merge $ctx (dict "argName" "hue" "argValue" (index $filterArgs 0) "min" 0 "max" 360) }}
|
||||
{{- template "validate-arg-value" $ctx }}
|
||||
{{- $ctx = merge $ctx (dict "argName" "saturation" "argValue" (index $filterArgs 1) "min" 0 "max" 100) }}
|
||||
{{- template "validate-arg-value" $ctx }}
|
||||
{{- $ctx = merge $ctx (dict "argName" "percentage" "argValue" (index $filterArgs 2) "min" 0 "max" 100) }}
|
||||
{{- template "validate-arg-value" $ctx }}
|
||||
{{- $f = images.Colorize (index $filterArgs 0) (index $filterArgs 1) (index $filterArgs 2) }}
|
||||
{{- else if eq $filter "contrast" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 1) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
{{- $filterArgs = apply $filterArgs "float" "." }}
|
||||
{{- $ctx = merge $ctx (dict "argName" "percentage" "argValue" (index $filterArgs 0) "min" -100 "max" 100) }}
|
||||
{{- template "validate-arg-value" $ctx }}
|
||||
{{- $f = images.Contrast (index $filterArgs 0) }}
|
||||
{{- else if eq $filter "dither" }}
|
||||
{{- $f = images.Dither }}
|
||||
{{- else if eq $filter "gamma" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 1) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
{{- $filterArgs = apply $filterArgs "float" "." }}
|
||||
{{- $ctx = merge $ctx (dict "argName" "gamma" "argValue" (index $filterArgs 0) "min" 0 "max" 100) }}
|
||||
{{- template "validate-arg-value" $ctx }}
|
||||
{{- $f = images.Gamma (index $filterArgs 0) }}
|
||||
{{- else if eq $filter "gaussianblur" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 1) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
{{- $filterArgs = apply $filterArgs "float" "." }}
|
||||
{{- $ctx = merge $ctx (dict "argName" "sigma" "argValue" (index $filterArgs 0) "min" 0 "max" 1000) }}
|
||||
{{- template "validate-arg-value" $ctx }}
|
||||
{{- $f = images.GaussianBlur (index $filterArgs 0) }}
|
||||
{{- else if eq $filter "grayscale" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 0) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
{{- $f = images.Grayscale }}
|
||||
{{- else if eq $filter "hue" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 1) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
{{- $filterArgs = apply $filterArgs "float" "." }}
|
||||
{{- $ctx = merge $ctx (dict "argName" "shift" "argValue" (index $filterArgs 0) "min" -180 "max" 180) }}
|
||||
{{- template "validate-arg-value" $ctx }}
|
||||
{{- $f = images.Hue (index $filterArgs 0) }}
|
||||
{{- else if eq $filter "invert" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 0) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
{{- $f = images.Invert }}
|
||||
{{- else if eq $filter "opacity" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 1) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
{{- $filterArgs = apply $filterArgs "float" "." }}
|
||||
{{- $ctx = merge $ctx (dict "argName" "opacity" "argValue" (index $filterArgs 0) "min" 0 "max" 1) }}
|
||||
{{- template "validate-arg-value" $ctx }}
|
||||
{{- $f = images.Opacity (index $filterArgs 0) }}
|
||||
{{- else if eq $filter "overlay" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 3) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
{{- $ctx := dict "src" (index $filterArgs 0) "name" .Name "position" .Position }}
|
||||
{{- $overlayImg := partial "inline/get-resource.html" $ctx }}
|
||||
{{- $f = images.Overlay $overlayImg (index $filterArgs 1 | float ) (index $filterArgs 2 | float) }}
|
||||
{{- else if eq $filter "padding" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 5) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
{{- $f = images.Padding
|
||||
(index $filterArgs 0 | int)
|
||||
(index $filterArgs 1 | int)
|
||||
(index $filterArgs 2 | int)
|
||||
(index $filterArgs 3 | int)
|
||||
(index $filterArgs 4)
|
||||
}}
|
||||
{{- else if eq $filter "pixelate" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 1) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
{{- $filterArgs = apply $filterArgs "float" "." }}
|
||||
{{- $ctx = merge $ctx (dict "argName" "size" "argValue" (index $filterArgs 0) "min" 0 "max" 1000) }}
|
||||
{{- template "validate-arg-value" $ctx }}
|
||||
{{- $f = images.Pixelate (index $filterArgs 0) }}
|
||||
{{- else if eq $filter "process" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 1) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
{{- $f = images.Process (index $filterArgs 0) }}
|
||||
{{- else if eq $filter "saturation" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 1) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
{{- $filterArgs = apply $filterArgs "float" "." }}
|
||||
{{- $ctx = merge $ctx (dict "argName" "percentage" "argValue" (index $filterArgs 0) "min" -100 "max" 500) }}
|
||||
{{- template "validate-arg-value" $ctx }}
|
||||
{{- $f = images.Saturation (index $filterArgs 0) }}
|
||||
{{- else if eq $filter "sepia" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 1) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
{{- $filterArgs = apply $filterArgs "float" "." }}
|
||||
{{- $ctx = merge $ctx (dict "argName" "percentage" "argValue" (index $filterArgs 0) "min" 0 "max" 100) }}
|
||||
{{- template "validate-arg-value" $ctx }}
|
||||
{{- $f = images.Sepia (index $filterArgs 0) }}
|
||||
{{- else if eq $filter "sigmoid" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 2) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
{{- $filterArgs = apply $filterArgs "float" "." }}
|
||||
{{- $ctx = merge $ctx (dict "argName" "midpoint" "argValue" (index $filterArgs 0) "min" 0 "max" 1) }}
|
||||
{{- template "validate-arg-value" $ctx }}
|
||||
{{- $ctx = merge $ctx (dict "argName" "factor" "argValue" (index $filterArgs 1) "min" -10 "max" 10) }}
|
||||
{{- template "validate-arg-value" $ctx }}
|
||||
{{- $f = images.Sigmoid (index $filterArgs 0) (index $filterArgs 1) }}
|
||||
{{- else if eq $filter "text" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 1) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
{{- $ctx := dict "src" $textFilterOpts.fontPath "name" .Name "position" .Position }}
|
||||
{{- $font := or (partial "inline/get-resource.html" $ctx) }}
|
||||
{{- $fontSize := or (index $filterArgs 3 | int) $textFilterOpts.fontSize }}
|
||||
{{- $lineHeight := math.Max (or (index $filterArgs 4 | float) $textFilterOpts.lineHeight) 1 }}
|
||||
{{- $opts := dict
|
||||
"x" (or (index $filterArgs 1 | int) $textFilterOpts.xOffset)
|
||||
"y" (or (index $filterArgs 2 | int) $textFilterOpts.yOffset)
|
||||
"size" $fontSize
|
||||
"linespacing" (mul (sub $lineHeight 1) $fontSize)
|
||||
"color" (or (index $filterArgs 5) $textFilterOpts.fontColor)
|
||||
"font" $font
|
||||
}}
|
||||
{{- $f = images.Text (index $filterArgs 0) $opts }}
|
||||
{{- else if eq $filter "unsharpmask" }}
|
||||
{{- $ctx = merge $ctx (dict "argsRequired" 3) }}
|
||||
{{- template "validate-arg-count" $ctx }}
|
||||
{{- $filterArgs = apply $filterArgs "float" "." }}
|
||||
{{- $ctx = merge $ctx (dict "argName" "sigma" "argValue" (index $filterArgs 0) "min" 0 "max" 500) }}
|
||||
{{- template "validate-arg-value" $ctx }}
|
||||
{{- $ctx = merge $ctx (dict "argName" "amount" "argValue" (index $filterArgs 1) "min" 0 "max" 100) }}
|
||||
{{- template "validate-arg-value" $ctx }}
|
||||
{{- $ctx = merge $ctx (dict "argName" "threshold" "argValue" (index $filterArgs 2) "min" 0 "max" 1) }}
|
||||
{{- template "validate-arg-value" $ctx }}
|
||||
{{- $f = images.UnsharpMask (index $filterArgs 0) (index $filterArgs 1) (index $filterArgs 2) }}
|
||||
{{- end }}
|
||||
|
||||
{{- /* Apply filter. */}}
|
||||
{{- $fi := $i }}
|
||||
{{- with $f }}
|
||||
{{- $fi = $i.Filter . }}
|
||||
{{- end }}
|
||||
|
||||
{{- /* Render. */}}
|
||||
{{- if $example }}
|
||||
<p>Original</p>
|
||||
<img class='di ba b--black-20' style="width: initial;" src="{{ $i.RelPermalink }}" alt="{{ $alt }}">
|
||||
<p>Processed</p>
|
||||
<img class='di ba b--black-20' style="width: initial;" src="{{ $fi.RelPermalink }}" alt="{{ $alt }}">
|
||||
{{- else -}}
|
||||
<img class='di' style="width: initial;" src="{{ $fi.RelPermalink }}" alt="{{ $alt }}">
|
||||
{{- end }}
|
||||
|
||||
{{- define "validate-arg-count" }}
|
||||
{{- $msg := "When using the %q filter, the %q shortcode requires an args parameter with %d %s. See %s" }}
|
||||
{{- if lt (len .args) .argsRequired }}
|
||||
{{- $text := "values" }}
|
||||
{{- if eq 1 .argsRequired }}
|
||||
{{- $text = "value" }}
|
||||
{{- end }}
|
||||
{{- errorf $msg .filter .name .argsRequired $text .position }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "validate-arg-value" }}
|
||||
{{- $msg := "The %q argument passed to the %q shortcode is invalid. Expected a value in the range [%v,%v], but received %v. See %s" }}
|
||||
{{- if or (lt .argValue .min) (gt .argValue .max) }}
|
||||
{{- errorf $msg .argName .name .min .max .argValue .position }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{- define "partials/inline/get-resource.html" }}
|
||||
{{- $r := "" }}
|
||||
{{- $u := urls.Parse .src }}
|
||||
{{- $msg := "The %q shortcode was unable to resolve %s. See %s" }}
|
||||
{{- if $u.IsAbs }}
|
||||
{{- with resources.GetRemote $u.String }}
|
||||
{{- with .Err }}
|
||||
{{- errorf "%s" }}
|
||||
{{- else }}
|
||||
{{- /* This is a remote resource. */}}
|
||||
{{- $r = . }}
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
{{- errorf $msg $.name $u.String $.position }}
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
{{- with .page.Resources.Get (strings.TrimPrefix "./" $u.Path) }}
|
||||
{{- /* This is a page resource. */}}
|
||||
{{- $r = . }}
|
||||
{{- else }}
|
||||
{{- with resources.Get $u.Path }}
|
||||
{{- /* This is a global resource. */}}
|
||||
{{- $r = . }}
|
||||
{{- else }}
|
||||
{{- errorf $msg $.name $u.Path $.position }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- return $r}}
|
||||
{{- end -}}
|
||||
@@ -2,10 +2,10 @@ module github.com/gohugoio/hugo
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69
|
||||
github.com/alecthomas/chroma/v2 v2.12.0
|
||||
github.com/alecthomas/chroma/v2 v2.13.0
|
||||
github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.32.6
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.35.4
|
||||
github.com/bep/clocks v0.5.0
|
||||
github.com/bep/debounce v1.2.0
|
||||
github.com/bep/gitmap v1.1.2
|
||||
@@ -18,24 +18,24 @@ require (
|
||||
github.com/bep/lazycache v0.4.0
|
||||
github.com/bep/logg v0.4.0
|
||||
github.com/bep/mclib v1.20400.20402
|
||||
github.com/bep/overlayfs v0.9.1
|
||||
github.com/bep/overlayfs v0.9.2
|
||||
github.com/bep/simplecobra v0.4.0
|
||||
github.com/bep/tmc v0.5.1
|
||||
github.com/clbanning/mxj/v2 v2.7.0
|
||||
github.com/cli/safeexec v1.0.1
|
||||
github.com/disintegration/gift v1.2.1
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/evanw/esbuild v0.20.0
|
||||
github.com/evanw/esbuild v0.20.2
|
||||
github.com/fatih/color v1.16.0
|
||||
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/passthrough 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
|
||||
github.com/gohugoio/testmodBuilder/mods v0.0.0-20190520184928-c56af20f2e95
|
||||
@@ -46,6 +46,7 @@ require (
|
||||
github.com/kylelemons/godebug v1.1.0
|
||||
github.com/kyokomi/emoji/v2 v2.2.12
|
||||
github.com/magefile/mage v1.15.0
|
||||
github.com/makeworld-the-better-one/dither/v2 v2.4.0
|
||||
github.com/marekm4/color-extractor v1.2.1
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mitchellh/hashstructure v1.1.0
|
||||
@@ -54,28 +55,28 @@ 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.1.1
|
||||
github.com/pelletier/go-toml/v2 v2.2.0
|
||||
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
|
||||
github.com/spf13/afero v1.11.0
|
||||
github.com/spf13/cast v1.6.0
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/spf13/fsync v0.10.0
|
||||
github.com/spf13/fsync v0.10.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/tdewolff/minify/v2 v2.20.16
|
||||
github.com/tdewolff/parse/v2 v2.7.11
|
||||
github.com/yuin/goldmark v1.7.0
|
||||
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.15.0
|
||||
golang.org/x/mod v0.15.0
|
||||
golang.org/x/net v0.20.0
|
||||
golang.org/x/sync v0.6.0
|
||||
golang.org/x/mod v0.17.0
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/text v0.14.0
|
||||
golang.org/x/tools v0.17.0
|
||||
golang.org/x/tools v0.20.0
|
||||
google.golang.org/api v0.152.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
@@ -99,8 +100,8 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.12 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect
|
||||
@@ -111,9 +112,9 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.18.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 // indirect
|
||||
github.com/aws/smithy-go v1.19.0 // indirect
|
||||
github.com/aws/smithy-go v1.20.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
|
||||
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.8 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.1.0 // indirect
|
||||
@@ -141,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.18.0 // indirect
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
golang.org/x/oauth2 v0.15.0 // indirect
|
||||
golang.org/x/sys v0.16.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
|
||||
|
||||
@@ -66,16 +66,16 @@ github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69/go.mod h1:L1AbZd
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
|
||||
github.com/alecthomas/chroma/v2 v2.12.0 h1:Wh8qLEgMMsN7mgyG8/qIpegky2Hvzr4By6gEF7cmWgw=
|
||||
github.com/alecthomas/chroma/v2 v2.12.0/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw=
|
||||
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
|
||||
github.com/alecthomas/assert/v2 v2.6.0 h1:o3WJwILtexrEUk3cUVal3oiQY2tfgr/FHWiz/v2n4FU=
|
||||
github.com/alecthomas/chroma/v2 v2.13.0 h1:VP72+99Fb2zEcYM0MeaWJmV+xQvz5v5cxRHd+ooU1lI=
|
||||
github.com/alecthomas/chroma/v2 v2.13.0/go.mod h1:BUGjjsD+ndS6eX37YgTchSEG+Jg9Jv1GiZs9sqPqztk=
|
||||
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||
github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c h1:651/eoCRnQ7YtSjAnSzRucrJz+3iGEFt+ysraELS81M=
|
||||
github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aws/aws-sdk-go v1.50.7 h1:odKb+uneeGgF2jgAerKjFzpljiyZxleV4SHB7oBK+YA=
|
||||
github.com/aws/aws-sdk-go v1.50.7/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU=
|
||||
github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4=
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA=
|
||||
github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.26.1 h1:z6DqMxclFGL3Zfo+4Q0rLnAZ6yVkzCRxhRMsiRQnD1o=
|
||||
@@ -86,16 +86,16 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10 h1:w98BT5w+ao1/r5sUuiH6Jk
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.10/go.mod h1:K2WGI7vUvkIv1HoNbfBA1bvIZ+9kL3YVmWxeKuLQsiw=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7 h1:FnLf60PtjXp8ZOzQfhJVsqF0OtYKQZWQfqOLshh8YXg=
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.7/go.mod h1:tDVvl8hyU6E9B8TrnNrZQEVkQlB8hjJwcgpPhgtlnNg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10 h1:vF+Zgd9s+H4vOXd5BMaPWykta2a6Ih0AKLq/X6NYKn4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.10/go.mod h1:6BkRjejp/GR4411UGqkX8+wFMbFbqsUIimfK4XjOKR4=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10 h1:nYPe006ktcqUji8S2mqXf9c/7NdiKriOwMvWQHgYztw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.10/go.mod h1:6UV4SZkVvmODfXKql4LCbaZUpF7HO2BX38FgBf9ZOLw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9 h1:ugD6qzjYtB7zM5PN/ZIeaAIyefPaD82G8+SJopgvUpw=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.9/go.mod h1:YD0aYBWCrPENpHolhKw2XDlTIWae2GKXT1T4o6N6hiM=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.32.6 h1:xKbFXea2CIF/Wskauz1TMr//wZ6FyzEafMdSBIQqn80=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.32.6/go.mod h1:iB6PQSb3ULRrrlEiuFfVE318JiBOdk4k46BbuzrrgXc=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.35.4 h1:a4gfRHHCzvV0jEjOUdZOK0oJ4H21x5WT+E4ucWk4jeM=
|
||||
github.com/aws/aws-sdk-go-v2/service/cloudfront v1.35.4/go.mod h1:Pphkts8iBnexoEpcMti5fUvN3/yoGRLtl2heOeppF70=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.9 h1:/90OR2XbSYfXucBMJ4U14wrjlfleq/0SB6dZDPncgmo=
|
||||
@@ -112,8 +112,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5 h1:2k9KmFawS63euAkY4/ixVNsY
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.5/go.mod h1:W+nd4wWDVkSUIox9bacmkBP5NMFQeTJ/xqNabpzSR38=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.5 h1:5UYvv8JUvllZsRnfrcMQ+hJ9jNICmcgKPAO1CER25Wg=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.26.5/go.mod h1:XX5gh4CB7wAs4KhcF46G6C8a2i7eupU19dcAAE+EydU=
|
||||
github.com/aws/smithy-go v1.19.0 h1:KWFKQV80DpP3vJrrA9sVAHQ5gc2z8i4EzrLhLlWXcBM=
|
||||
github.com/aws/smithy-go v1.19.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
|
||||
github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
|
||||
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
|
||||
github.com/bep/clocks v0.5.0 h1:hhvKVGLPQWRVsBP/UB7ErrHYIO42gINVbvqxvYTPVps=
|
||||
github.com/bep/clocks v0.5.0/go.mod h1:SUq3q+OOq41y2lRQqH5fsOoxN8GbxSiT6jvoVVLCVhU=
|
||||
github.com/bep/debounce v1.2.0 h1:wXds8Kq8qRfwAOpAxHrJDbCXgC5aHSzgQb/0gKsHQqo=
|
||||
@@ -138,8 +138,8 @@ github.com/bep/logg v0.4.0 h1:luAo5mO4ZkhA5M1iDVDqDqnBBnlHjmtZF6VAyTp+nCQ=
|
||||
github.com/bep/logg v0.4.0/go.mod h1:Ccp9yP3wbR1mm++Kpxet91hAZBEQgmWgFgnXX3GkIV0=
|
||||
github.com/bep/mclib v1.20400.20402 h1:olpCE2WSPpOAbFE1R4hnftSEmQ34+xzy2HRzd0m69rA=
|
||||
github.com/bep/mclib v1.20400.20402/go.mod h1:pkrk9Kyfqg34Uj6XlDq9tdEFJBiL1FvCoCgVKRzw1EY=
|
||||
github.com/bep/overlayfs v0.9.1 h1:SL54SV8A3zRkmQ+83Jj4TLE88jadHd5d1L4NpfmqJJs=
|
||||
github.com/bep/overlayfs v0.9.1/go.mod h1:aYY9W7aXQsGcA7V9x/pzeR8LjEgIxbtisZm8Q7zPz40=
|
||||
github.com/bep/overlayfs v0.9.2 h1:qJEmFInsW12L7WW7dOTUhnMfyk/fN9OCDEO5Gr8HSDs=
|
||||
github.com/bep/overlayfs v0.9.2/go.mod h1:aYY9W7aXQsGcA7V9x/pzeR8LjEgIxbtisZm8Q7zPz40=
|
||||
github.com/bep/simplecobra v0.4.0 h1:ufX/6WcOtEVJdCd7hsztTWURlZkOaWYOD+zCqrM8qUE=
|
||||
github.com/bep/simplecobra v0.4.0/go.mod h1:evSM6iQqRwqpV7W4H4DlYFfe9mZ0x6Hj5GEOnIV7dI4=
|
||||
github.com/bep/tmc v0.5.1 h1:CsQnSC6MsomH64gw0cT5f+EwQDcvZz4AazKunFwTpuI=
|
||||
@@ -168,8 +168,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc=
|
||||
github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI=
|
||||
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
@@ -179,8 +179,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
||||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanw/esbuild v0.20.0 h1:pcW+/LCNc99Pgfs0kUnvjRCba8Lr9tDMSVg89t1ZLW4=
|
||||
github.com/evanw/esbuild v0.20.0/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
|
||||
github.com/evanw/esbuild v0.20.2 h1:E4Y0iJsothpUCq7y0D+ERfqpJmPWrZpNybJA3x3I4p8=
|
||||
github.com/evanw/esbuild v0.20.2/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||
@@ -193,8 +193,8 @@ 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=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
@@ -211,8 +211,8 @@ 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/passthrough v0.1.0 h1:oFQ3f1M3Ook6amHmbqVu/uBRrQ6yjMDFkIv4HQr0f1Y=
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.1.0/go.mod h1:g9CCh+Ci2IMbPUrVJuXbBTrA+rIIx5+hDQ4EXYaQDoM=
|
||||
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=
|
||||
github.com/gohugoio/locales v0.14.0/go.mod h1:ip8cCAv/cnmVLzzXtiTpPwgJ4xhKZranqNqtoIu0b/4=
|
||||
github.com/gohugoio/localescompressed v1.0.1 h1:KTYMi8fCWYLswFyJAeOtuk/EkXR/KPTHHNN9OS+RTxo=
|
||||
@@ -347,6 +347,8 @@ github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/makeworld-the-better-one/dither/v2 v2.4.0 h1:Az/dYXiTcwcRSe59Hzw4RI1rSnAZns+1msaCXetrMFE=
|
||||
github.com/makeworld-the-better-one/dither/v2 v2.4.0/go.mod h1:VBtN8DXO7SNtyGmLiGA7IsFeKrBkQPze1/iAeM95arc=
|
||||
github.com/marekm4/color-extractor v1.2.1 h1:3Zb2tQsn6bITZ8MBVhc33Qn1k5/SEuZ18mrXGUqIwn0=
|
||||
github.com/marekm4/color-extractor v1.2.1/go.mod h1:90VjmiHI6M8ez9eYUaXLdcKnS+BAOp7w+NpwBdkJmpA=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
@@ -374,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.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
|
||||
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||
github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo=
|
||||
github.com/pelletier/go-toml/v2 v2.2.0/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=
|
||||
@@ -408,13 +410,14 @@ github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/fsync v0.10.0 h1:j+zUMN41zWj3sEqueD4mAsPDQwyOvMeJCcrawdmbqXk=
|
||||
github.com/spf13/fsync v0.10.0/go.mod h1:y+B41vYq5i6Boa3Z+BVoPbDeOvxVkNU5OBXhoT8i4TQ=
|
||||
github.com/spf13/fsync v0.10.1 h1:JRnB7G72b+gIBaBcpn5ibJSd7ww1iEahXSX2B8G6dSE=
|
||||
github.com/spf13/fsync v0.10.1/go.mod h1:y+B41vYq5i6Boa3Z+BVoPbDeOvxVkNU5OBXhoT8i4TQ=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
@@ -423,12 +426,13 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tdewolff/minify/v2 v2.20.16 h1:/C8dtRkxLTIyUlKlBz46gDiktCrE8a6+c1gTrnPFz+U=
|
||||
github.com/tdewolff/minify/v2 v2.20.16/go.mod h1:/FvxV9KaTrFu35J9I2FhRvWSBxcHj8sDSdwBFh5voxM=
|
||||
github.com/tdewolff/parse/v2 v2.7.11 h1:v+W45LnzmjndVlfqPCT5gGjAAZKd1GJGOPJveTIkBY8=
|
||||
github.com/tdewolff/parse/v2 v2.7.11/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
|
||||
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.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=
|
||||
@@ -439,8 +443,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA=
|
||||
github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=
|
||||
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
github.com/yuin/goldmark-emoji v1.0.2 h1:c/RgTShNgHTtc6xdz2KKI74jJr6rWi7FPgnP9GAsO5s=
|
||||
github.com/yuin/goldmark-emoji v1.0.2/go.mod h1:RhP/RWpexdp+KHs7ghKnifRoIs/Bq4nDS7tRbCkOwKY=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
@@ -464,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.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||
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=
|
||||
@@ -506,8 +510,8 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -543,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.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
|
||||
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
|
||||
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=
|
||||
@@ -568,8 +572,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -613,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.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.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=
|
||||
@@ -683,8 +687,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
|
||||
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
@@ -50,6 +50,14 @@ func TestTrimShortHTML(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTrimShortHTML(b *testing.B) {
|
||||
c := newTestContentSpec(nil)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
c.TrimShortHTML([]byte("<p>Simple paragraph</p>"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBytesToHTML(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
c.Assert(helpers.BytesToHTML([]byte("dobedobedo")), qt.Equals, template.HTML("dobedobedo"))
|
||||
|
||||
+9
-2
@@ -188,7 +188,7 @@ func ReaderContains(r io.Reader, subslice []byte) bool {
|
||||
//
|
||||
// - "Go" (strings.Title)
|
||||
// - "AP" (see https://www.apstylebook.com/)
|
||||
// - "Chicago" (see http://www.chicagomanualofstyle.org/home.html)
|
||||
// - "Chicago" (see https://www.chicagomanualofstyle.org/home.html)
|
||||
// - "FirstUpper" (only the first character is upper case)
|
||||
// - "None" (no transformation)
|
||||
//
|
||||
@@ -328,7 +328,14 @@ func PrintFs(fs afero.Fs, path string, w io.Writer) {
|
||||
}
|
||||
|
||||
afero.Walk(fs, path, func(path string, info os.FileInfo, err error) error {
|
||||
fmt.Fprintln(w, filepath.ToSlash(path))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("error: path %q: %s", path, err))
|
||||
}
|
||||
path = filepath.ToSlash(path)
|
||||
if path == "" {
|
||||
path = "."
|
||||
}
|
||||
fmt.Fprintln(w, path, info.IsDir())
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
+1
-1
@@ -374,7 +374,7 @@ func cacheDirDefault(cacheDir string) string {
|
||||
// Turns out that Cloudflare also sets NETLIFY=true in its build environment,
|
||||
// but all of these 3 should not give any false positives.
|
||||
if os.Getenv("NETLIFY") == "true" && os.Getenv("PULL_REQUEST") != "" && os.Getenv("DEPLOY_PRIME_URL") != "" {
|
||||
// Netlify's cache behaviour is not documented, the currently best example
|
||||
// Netlify's cache behavior is not documented, the currently best example
|
||||
// is this project:
|
||||
// https://github.com/philhawksworth/content-shards/blob/master/gulpfile.js
|
||||
return "/opt/build/cache/hugo_cache/"
|
||||
|
||||
@@ -59,7 +59,7 @@ type componentFsDir struct {
|
||||
fs *componentFs
|
||||
}
|
||||
|
||||
// ReadDir reads count entries from this virtual directorie and
|
||||
// ReadDir reads count entries from this virtual directory and
|
||||
// sorts the entries according to the component filesystem rules.
|
||||
func (f *componentFsDir) ReadDir(count int) ([]iofs.DirEntry, error) {
|
||||
fis, err := f.DirOnlyOps.(iofs.ReadDirFile).ReadDir(-1)
|
||||
@@ -241,7 +241,7 @@ type ComponentFsOptions struct {
|
||||
DefaultContentLanguage string
|
||||
|
||||
// The parser used to parse paths provided by this filesystem.
|
||||
PathParser paths.PathParser
|
||||
PathParser *paths.PathParser
|
||||
}
|
||||
|
||||
func (fs *componentFs) Open(name string) (afero.File, error) {
|
||||
|
||||
@@ -42,7 +42,7 @@ var LanguageDirsMerger overlayfs.DirsMerger = func(lofi, bofi []fs.DirEntry) []f
|
||||
|
||||
// AppendDirsMerger merges two directories keeping all regular files
|
||||
// with the first slice as the base.
|
||||
// Duplicate directories in the secnond slice will be ignored.
|
||||
// Duplicate directories in the second slice will be ignored.
|
||||
// This strategy is used for the i18n and data fs where we need all entries.
|
||||
var AppendDirsMerger overlayfs.DirsMerger = func(lofi, bofi []fs.DirEntry) []fs.DirEntry {
|
||||
for _, fi1 := range bofi {
|
||||
|
||||
+3
-1
@@ -73,7 +73,9 @@ type FileMeta struct {
|
||||
InclusionFilter *glob.FilenameFilter
|
||||
|
||||
// Rename the name part of the file (not the directory).
|
||||
Rename func(name string, toFrom bool) string
|
||||
// Returns the new name and a boolean indicating if the file
|
||||
// should be included.
|
||||
Rename func(name string, toFrom bool) (string, bool)
|
||||
}
|
||||
|
||||
func (m *FileMeta) Copy() *FileMeta {
|
||||
|
||||
+140
-119
@@ -1,4 +1,4 @@
|
||||
// Copyright 2019 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
@@ -43,7 +44,7 @@ var _ ReverseLookupProvder = (*RootMappingFs)(nil)
|
||||
func NewRootMappingFs(fs afero.Fs, rms ...RootMapping) (*RootMappingFs, error) {
|
||||
rootMapToReal := radix.New()
|
||||
realMapToRoot := radix.New()
|
||||
var virtualRoots []RootMapping
|
||||
id := fmt.Sprintf("rfs-%d", rootMappingFsCounter.Add(1))
|
||||
|
||||
addMapping := func(key string, rm RootMapping, to *radix.Tree) {
|
||||
var mappings []RootMapping
|
||||
@@ -77,45 +78,49 @@ func NewRootMappingFs(fs afero.Fs, rms ...RootMapping) (*RootMappingFs, error) {
|
||||
rm.Meta = NewFileMeta()
|
||||
}
|
||||
|
||||
if rm.FromBase == "" {
|
||||
panic(" rm.FromBase is empty")
|
||||
}
|
||||
|
||||
rm.Meta.Component = rm.FromBase
|
||||
rm.Meta.Module = rm.Module
|
||||
rm.Meta.ModuleOrdinal = rm.ModuleOrdinal
|
||||
rm.Meta.IsProject = rm.IsProject
|
||||
rm.Meta.BaseDir = rm.ToBase
|
||||
|
||||
if !fi.IsDir() {
|
||||
// We do allow single file mounts.
|
||||
// However, the file system logic will be much simpler with just directories.
|
||||
// So, convert this mount into a directory mount with a nameTo filter and renamer.
|
||||
// So, convert this mount into a directory mount with a renamer,
|
||||
// which will tell the caller if name should be included.
|
||||
dirFrom, nameFrom := filepath.Split(rm.From)
|
||||
dirTo, nameTo := filepath.Split(rm.To)
|
||||
dirFrom, dirTo = strings.TrimSuffix(dirFrom, filepathSeparator), strings.TrimSuffix(dirTo, filepathSeparator)
|
||||
rm.From = dirFrom
|
||||
|
||||
fi, err = fs.Stat(rm.To)
|
||||
if err != nil {
|
||||
if herrors.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rm.fiSingleFile = NewFileMetaInfo(fi, rm.Meta.Copy())
|
||||
singleFileMeta := rm.Meta.Copy()
|
||||
singleFileMeta.Name = nameFrom
|
||||
rm.fiSingleFile = NewFileMetaInfo(fi, singleFileMeta)
|
||||
rm.To = dirTo
|
||||
|
||||
rm.Meta.Rename = func(name string, toFrom bool) string {
|
||||
rm.Meta.Rename = func(name string, toFrom bool) (string, bool) {
|
||||
if toFrom {
|
||||
if name == nameTo {
|
||||
return nameFrom
|
||||
return nameFrom, true
|
||||
}
|
||||
return name
|
||||
return "", false
|
||||
}
|
||||
|
||||
if name == nameFrom {
|
||||
return nameTo
|
||||
return nameTo, true
|
||||
}
|
||||
|
||||
return name
|
||||
return "", false
|
||||
}
|
||||
nameToFilename := filepathSeparator + nameTo
|
||||
|
||||
rm.Meta.InclusionFilter = rm.Meta.InclusionFilter.Append(glob.NewFilenameFilterForInclusionFunc(
|
||||
func(filename string) bool {
|
||||
return strings.HasPrefix(nameToFilename, filename)
|
||||
return nameToFilename == filename
|
||||
},
|
||||
))
|
||||
|
||||
@@ -129,19 +134,9 @@ func NewRootMappingFs(fs afero.Fs, rms ...RootMapping) (*RootMappingFs, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if rm.FromBase == "" {
|
||||
panic(" rm.FromBase is empty")
|
||||
}
|
||||
|
||||
// Extract "blog" from "content/blog"
|
||||
rm.path = strings.TrimPrefix(strings.TrimPrefix(rm.From, rm.FromBase), filepathSeparator)
|
||||
|
||||
rm.Meta.SourceRoot = fi.(MetaProvider).Meta().Filename
|
||||
rm.Meta.BaseDir = rm.ToBase
|
||||
rm.Meta.Module = rm.Module
|
||||
rm.Meta.ModuleOrdinal = rm.ModuleOrdinal
|
||||
rm.Meta.Component = rm.FromBase
|
||||
rm.Meta.IsProject = rm.IsProject
|
||||
|
||||
meta := rm.Meta.Copy()
|
||||
|
||||
@@ -160,12 +155,10 @@ func NewRootMappingFs(fs afero.Fs, rms ...RootMapping) (*RootMappingFs, error) {
|
||||
|
||||
addMapping(rev, rm, realMapToRoot)
|
||||
|
||||
virtualRoots = append(virtualRoots, rm)
|
||||
}
|
||||
|
||||
rootMapToReal.Insert(filepathSeparator, virtualRoots)
|
||||
|
||||
rfs := &RootMappingFs{
|
||||
id: id,
|
||||
Fs: fs,
|
||||
rootMapToReal: rootMapToReal,
|
||||
realMapToRoot: realMapToRoot,
|
||||
@@ -237,11 +230,14 @@ var _ FilesystemUnwrapper = (*RootMappingFs)(nil)
|
||||
// is directories only, and they will be returned in Readdir and Readdirnames
|
||||
// in the order given.
|
||||
type RootMappingFs struct {
|
||||
id string
|
||||
afero.Fs
|
||||
rootMapToReal *radix.Tree
|
||||
realMapToRoot *radix.Tree
|
||||
}
|
||||
|
||||
var rootMappingFsCounter atomic.Int32
|
||||
|
||||
func (fs *RootMappingFs) Mounts(base string) ([]FileMetaInfo, error) {
|
||||
base = filepathSeparator + fs.cleanName(base)
|
||||
roots := fs.getRootsWithPrefix(base)
|
||||
@@ -252,13 +248,11 @@ func (fs *RootMappingFs) Mounts(base string) ([]FileMetaInfo, error) {
|
||||
|
||||
fss := make([]FileMetaInfo, len(roots))
|
||||
for i, r := range roots {
|
||||
|
||||
if r.fiSingleFile != nil {
|
||||
// A single file mount.
|
||||
fss[i] = r.fiSingleFile
|
||||
continue
|
||||
}
|
||||
|
||||
bfs := NewBasePathFs(fs.Fs, r.To)
|
||||
fs := bfs
|
||||
if r.Meta.InclusionFilter != nil {
|
||||
@@ -269,17 +263,16 @@ func (fs *RootMappingFs) Mounts(base string) ([]FileMetaInfo, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("RootMappingFs.Dirs: %w", err)
|
||||
}
|
||||
|
||||
if !fi.IsDir() {
|
||||
fi.(FileMetaInfo).Meta().Merge(r.Meta)
|
||||
}
|
||||
|
||||
fss[i] = fi.(FileMetaInfo)
|
||||
}
|
||||
|
||||
return fss, nil
|
||||
}
|
||||
|
||||
func (fs *RootMappingFs) Key() string {
|
||||
return fs.id
|
||||
}
|
||||
|
||||
func (fs *RootMappingFs) UnwrapFilesystem() afero.Fs {
|
||||
return fs.Fs
|
||||
}
|
||||
@@ -323,7 +316,6 @@ func (fs *RootMappingFs) Stat(name string) (os.FileInfo, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fis[0], nil
|
||||
}
|
||||
|
||||
@@ -338,13 +330,18 @@ func (c ComponentPath) ComponentPathJoined() string {
|
||||
}
|
||||
|
||||
type ReverseLookupProvder interface {
|
||||
ReverseLookup(filename string, checkExists bool) ([]ComponentPath, error)
|
||||
ReverseLookup(filename string) ([]ComponentPath, error)
|
||||
ReverseLookupComponent(component, filename string) ([]ComponentPath, error)
|
||||
}
|
||||
|
||||
// func (fs *RootMappingFs) ReverseStat(filename string) ([]FileMetaInfo, error)
|
||||
func (fs *RootMappingFs) ReverseLookup(in string, checkExists bool) ([]ComponentPath, error) {
|
||||
in = fs.cleanName(in)
|
||||
key := filepathSeparator + in
|
||||
func (fs *RootMappingFs) ReverseLookup(filename string) ([]ComponentPath, error) {
|
||||
return fs.ReverseLookupComponent("", filename)
|
||||
}
|
||||
|
||||
func (fs *RootMappingFs) ReverseLookupComponent(component, filename string) ([]ComponentPath, error) {
|
||||
filename = fs.cleanName(filename)
|
||||
key := filepathSeparator + filename
|
||||
|
||||
s, roots := fs.getRootsReverse(key)
|
||||
|
||||
@@ -358,19 +355,21 @@ func (fs *RootMappingFs) ReverseLookup(in string, checkExists bool) ([]Component
|
||||
dir, name := filepath.Split(base)
|
||||
|
||||
for _, first := range roots {
|
||||
if first.Meta.Rename != nil {
|
||||
name = first.Meta.Rename(name, true)
|
||||
if component != "" && first.FromBase != component {
|
||||
continue
|
||||
}
|
||||
|
||||
// Now we know that this file _could_ be in this fs.
|
||||
filename := filepathSeparator + filepath.Join(first.path, dir, name)
|
||||
|
||||
if checkExists {
|
||||
// Confirm that it exists.
|
||||
_, err := fs.Stat(first.FromBase + filename)
|
||||
if err != nil {
|
||||
var filename string
|
||||
if first.Meta.Rename != nil {
|
||||
// Single file mount.
|
||||
if newname, ok := first.Meta.Rename(name, true); ok {
|
||||
filename = filepathSeparator + filepath.Join(first.path, dir, newname)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
// Now we know that this file _could_ be in this fs.
|
||||
filename = filepathSeparator + filepath.Join(first.path, dir, name)
|
||||
}
|
||||
|
||||
cps = append(cps, ComponentPath{
|
||||
@@ -403,16 +402,43 @@ func (fs *RootMappingFs) getRoot(key string) []RootMapping {
|
||||
}
|
||||
|
||||
func (fs *RootMappingFs) getRoots(key string) (string, []RootMapping) {
|
||||
return fs.getRootsIn(key, fs.rootMapToReal)
|
||||
tree := fs.rootMapToReal
|
||||
levels := strings.Count(key, filepathSeparator)
|
||||
seen := make(map[RootMapping]bool)
|
||||
|
||||
var roots []RootMapping
|
||||
var s string
|
||||
|
||||
for {
|
||||
var found bool
|
||||
ss, vv, found := tree.LongestPrefix(key)
|
||||
|
||||
if !found || (levels < 2 && ss == key) {
|
||||
break
|
||||
}
|
||||
|
||||
for _, rm := range vv.([]RootMapping) {
|
||||
if !seen[rm] {
|
||||
seen[rm] = true
|
||||
roots = append(roots, rm)
|
||||
}
|
||||
}
|
||||
s = ss
|
||||
|
||||
// We may have more than one root for this key, so walk up.
|
||||
oldKey := key
|
||||
key = filepath.Dir(key)
|
||||
if key == oldKey {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return s, roots
|
||||
}
|
||||
|
||||
func (fs *RootMappingFs) getRootsReverse(key string) (string, []RootMapping) {
|
||||
return fs.getRootsIn(key, fs.realMapToRoot)
|
||||
}
|
||||
|
||||
func (fs *RootMappingFs) getRootsIn(key string, tree *radix.Tree) (string, []RootMapping) {
|
||||
tree := fs.realMapToRoot
|
||||
s, v, found := tree.LongestPrefix(key)
|
||||
|
||||
if !found {
|
||||
return "", nil
|
||||
}
|
||||
@@ -449,6 +475,11 @@ func (fs *RootMappingFs) newUnionFile(fis ...FileMetaInfo) (afero.File, error) {
|
||||
return fis[0].Meta().Open()
|
||||
}
|
||||
|
||||
if !fis[0].IsDir() {
|
||||
// Pick the last file mount.
|
||||
return fis[len(fis)-1].Meta().Open()
|
||||
}
|
||||
|
||||
openers := make([]func() (afero.File, error), len(fis))
|
||||
for i := len(fis) - 1; i >= 0; i-- {
|
||||
fi := fis[i]
|
||||
@@ -537,9 +568,11 @@ func (rfs *RootMappingFs) collectDirEntries(prefix string) ([]iofs.DirEntry, err
|
||||
}
|
||||
fi = newDirNameOnlyFileInfo(name, meta, opener)
|
||||
} else if rm.Meta.Rename != nil {
|
||||
if n := rm.Meta.Rename(fi.Name(), true); n != fi.Name() {
|
||||
fi.(MetaProvider).Meta().Name = n
|
||||
n, ok := rm.Meta.Rename(fi.Name(), true)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
fi.(MetaProvider).Meta().Name = n
|
||||
}
|
||||
fis = append(fis, fi)
|
||||
}
|
||||
@@ -584,25 +617,16 @@ func (rfs *RootMappingFs) collectDirEntries(prefix string) ([]iofs.DirEntry, err
|
||||
|
||||
rms := v.([]RootMapping)
|
||||
for _, rm := range rms {
|
||||
if !rm.fi.IsDir() {
|
||||
// A single file mount
|
||||
fis = append(fis, rm.fi)
|
||||
continue
|
||||
}
|
||||
name := filepath.Base(rm.From)
|
||||
if seen[name] {
|
||||
continue
|
||||
}
|
||||
seen[name] = true
|
||||
|
||||
opener := func() (afero.File, error) {
|
||||
return rfs.Open(rm.From)
|
||||
}
|
||||
|
||||
fi := newDirNameOnlyFileInfo(name, rm.Meta, opener)
|
||||
|
||||
fis = append(fis, fi)
|
||||
|
||||
}
|
||||
|
||||
return false
|
||||
@@ -628,6 +652,28 @@ func (rfs *RootMappingFs) collectDirEntries(prefix string) ([]iofs.DirEntry, err
|
||||
}
|
||||
|
||||
func (fs *RootMappingFs) doStat(name string) ([]FileMetaInfo, error) {
|
||||
fis, err := fs.doDoStat(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Sanity check. Check that all is either file or directories.
|
||||
var isDir, isFile bool
|
||||
for _, fi := range fis {
|
||||
if fi.IsDir() {
|
||||
isDir = true
|
||||
} else {
|
||||
isFile = true
|
||||
}
|
||||
}
|
||||
if isDir && isFile {
|
||||
// For now.
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
return fis, nil
|
||||
}
|
||||
|
||||
func (fs *RootMappingFs) doDoStat(name string) ([]FileMetaInfo, error) {
|
||||
name = fs.cleanName(name)
|
||||
key := filepathSeparator + name
|
||||
|
||||
@@ -668,48 +714,20 @@ func (fs *RootMappingFs) doStat(name string) ([]FileMetaInfo, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fileCount := 0
|
||||
var wasFiltered bool
|
||||
for _, root := range roots {
|
||||
meta := root.fi.Meta()
|
||||
if !meta.InclusionFilter.Match(strings.TrimPrefix(meta.Filename, meta.SourceRoot), root.fi.IsDir()) {
|
||||
wasFiltered = true
|
||||
continue
|
||||
}
|
||||
|
||||
if !root.fi.IsDir() {
|
||||
fileCount++
|
||||
}
|
||||
if fileCount > 1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if fileCount == 0 {
|
||||
if wasFiltered {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
// Dir only.
|
||||
return []FileMetaInfo{newDirNameOnlyFileInfo(name, roots[0].Meta, fs.virtualDirOpener(name))}, nil
|
||||
}
|
||||
|
||||
if fileCount > 1 {
|
||||
// Not supported by this filesystem.
|
||||
return nil, fmt.Errorf("found multiple files with name %q, use .Readdir or the source filesystem directly", name)
|
||||
}
|
||||
|
||||
return []FileMetaInfo{roots[0].fi}, nil
|
||||
return []FileMetaInfo{newDirNameOnlyFileInfo(name, roots[0].Meta, fs.virtualDirOpener(name))}, nil
|
||||
}
|
||||
|
||||
func (fs *RootMappingFs) statRoot(root RootMapping, filename string) (FileMetaInfo, error) {
|
||||
dir, name := filepath.Split(filename)
|
||||
if root.Meta.Rename != nil {
|
||||
if n := root.Meta.Rename(name, false); n != name {
|
||||
filename = filepath.Join(dir, n)
|
||||
n, ok := root.Meta.Rename(name, false)
|
||||
if !ok {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
filename = filepath.Join(dir, n)
|
||||
}
|
||||
|
||||
if !root.Meta.InclusionFilter.Match(root.trimFrom(filename), root.fi.IsDir()) {
|
||||
if !root.Meta.InclusionFilter.Match(root.trimFrom(filename), true) {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
@@ -720,24 +738,27 @@ func (fs *RootMappingFs) statRoot(root RootMapping, filename string) (FileMetaIn
|
||||
}
|
||||
|
||||
var opener func() (afero.File, error)
|
||||
if fi.IsDir() {
|
||||
// Make sure metadata gets applied in ReadDir.
|
||||
opener = fs.realDirOpener(filename, root.Meta)
|
||||
} else {
|
||||
if root.Meta.Rename != nil {
|
||||
if n := root.Meta.Rename(fi.Name(), true); n != fi.Name() {
|
||||
meta := fi.(MetaProvider).Meta()
|
||||
|
||||
meta.Name = n
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if !fi.IsDir() {
|
||||
// Open the file directly.
|
||||
// Opens the real file directly.
|
||||
opener = func() (afero.File, error) {
|
||||
return fs.Fs.Open(filename)
|
||||
}
|
||||
|
||||
} else if root.Meta.Rename != nil {
|
||||
// A single file mount where we have mounted the containing directory.
|
||||
n, ok := root.Meta.Rename(fi.Name(), true)
|
||||
if !ok {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
meta := fi.(MetaProvider).Meta()
|
||||
meta.Name = n
|
||||
// Opens the real file directly.
|
||||
opener = func() (afero.File, error) {
|
||||
return fs.Fs.Open(filename)
|
||||
}
|
||||
} else {
|
||||
// Make sure metadata gets applied in ReadDir.
|
||||
opener = fs.realDirOpener(filename, root.Meta)
|
||||
}
|
||||
|
||||
fim := decorateFileInfo(fi, opener, "", root.Meta)
|
||||
@@ -801,7 +822,7 @@ func (f *rootMappingDir) ReadDir(count int) ([]iofs.DirEntry, error) {
|
||||
return f.fs.collectDirEntries(f.name)
|
||||
}
|
||||
|
||||
// Sentinal error to signal that a file is a directory.
|
||||
// Sentinel error to signal that a file is a directory.
|
||||
var errIsDir = errors.New("isDir")
|
||||
|
||||
func (f *rootMappingDir) Stat() (iofs.FileInfo, error) {
|
||||
|
||||
@@ -276,15 +276,20 @@ func TestRootMappingFsMount(t *testing.T) {
|
||||
|
||||
// Test ReverseLookup.
|
||||
// Single file mounts.
|
||||
cps, err := rfs.ReverseLookup(filepath.FromSlash("singlefiles/no.txt"), true)
|
||||
cps, err := rfs.ReverseLookup(filepath.FromSlash("singlefiles/no.txt"))
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(cps, qt.DeepEquals, []ComponentPath{
|
||||
{Component: "content", Path: "singles/p1.md", Lang: "no"},
|
||||
})
|
||||
|
||||
cps, err = rfs.ReverseLookup(filepath.FromSlash("singlefiles/sv.txt"))
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(cps, qt.DeepEquals, []ComponentPath{
|
||||
{Component: "content", Path: "singles/p1.md", Lang: "sv"},
|
||||
})
|
||||
|
||||
// File inside directory mount.
|
||||
cps, err = rfs.ReverseLookup(filepath.FromSlash("mynoblogcontent/test.txt"), true)
|
||||
cps, err = rfs.ReverseLookup(filepath.FromSlash("mynoblogcontent/test.txt"))
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(cps, qt.DeepEquals, []ComponentPath{
|
||||
{Component: "content", Path: "blog/test.txt", Lang: "no"},
|
||||
|
||||
@@ -674,6 +674,8 @@ S1|p1:|p2:p2|
|
||||
|
||||
// Issue 11977.
|
||||
func TestCascadeExtensionInPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.org"
|
||||
@@ -700,3 +702,93 @@ title: "Post 1"
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
b.AssertLogContains(`cascade target path "/posts/post-1.de.md" looks like a path with an extension; since Hugo v0.123.0 this will not match anything, see https://gohugo.io/methods/page/path/`)
|
||||
}
|
||||
|
||||
func TestCascadeExtensionInPathIgnore(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.org"
|
||||
ignoreLogs = ['cascade-pattern-with-extension']
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
[languages.de]
|
||||
-- content/_index.de.md --
|
||||
+++
|
||||
[[cascade]]
|
||||
[cascade.params]
|
||||
foo = 'bar'
|
||||
[cascade._target]
|
||||
path = '/posts/post-1.de.md'
|
||||
+++
|
||||
-- content/posts/post-1.de.md --
|
||||
---
|
||||
title: "Post 1"
|
||||
---
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Title }}|{{ .Params.foo }}$
|
||||
`
|
||||
b := Test(t, files)
|
||||
b.AssertLogNotContains(`looks like a path with an extension`)
|
||||
}
|
||||
|
||||
func TestCascadConfigExtensionInPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.org"
|
||||
[[cascade]]
|
||||
[cascade.params]
|
||||
foo = 'bar'
|
||||
[cascade._target]
|
||||
path = '/p1.md'
|
||||
`
|
||||
b, err := TestE(t, files)
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
b.AssertLogContains(`looks like a path with an extension`)
|
||||
}
|
||||
|
||||
func TestCascadConfigExtensionInPathIgnore(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.org"
|
||||
ignoreLogs = ['cascade-pattern-with-extension']
|
||||
[[cascade]]
|
||||
[cascade.params]
|
||||
foo = 'bar'
|
||||
[cascade._target]
|
||||
path = '/p1.md'
|
||||
`
|
||||
b := Test(t, files)
|
||||
b.AssertLogNotContains(`looks like a path with an extension`)
|
||||
}
|
||||
|
||||
func TestCascadeIssue12172(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['rss','sitemap','taxonomy','term']
|
||||
[[cascade]]
|
||||
headless = true
|
||||
[cascade._target]
|
||||
path = '/s1**'
|
||||
-- content/s1/p1.md --
|
||||
---
|
||||
title: p1
|
||||
---
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Title }}|
|
||||
-- layouts/_default/list.html --
|
||||
{{ .Title }}|
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileExists("public/index.html", true)
|
||||
b.AssertFileExists("public/s1/index.html", false)
|
||||
b.AssertFileExists("public/s1/p1/index.html", false)
|
||||
}
|
||||
|
||||
+43
-24
@@ -490,11 +490,10 @@ name = "menu-theme"
|
||||
got := b.Configs.Base
|
||||
|
||||
if mergeStrategy == "none" {
|
||||
b.Assert(got.Sitemap, qt.DeepEquals, config.SitemapConfig{ChangeFreq: "", Priority: -1, Filename: "sitemap.xml"})
|
||||
|
||||
b.Assert(got.Sitemap, qt.DeepEquals, config.SitemapConfig{ChangeFreq: "", Disable: false, Priority: -1, Filename: "sitemap.xml"})
|
||||
b.AssertFileContent("public/sitemap.xml", "schemas/sitemap")
|
||||
} else {
|
||||
b.Assert(got.Sitemap, qt.DeepEquals, config.SitemapConfig{ChangeFreq: "monthly", Priority: -1, Filename: "sitemap.xml"})
|
||||
b.Assert(got.Sitemap, qt.DeepEquals, config.SitemapConfig{ChangeFreq: "monthly", Disable: false, Priority: -1, Filename: "sitemap.xml"})
|
||||
b.AssertFileContent("public/sitemap.xml", "<changefreq>monthly</changefreq>")
|
||||
}
|
||||
})
|
||||
@@ -508,12 +507,12 @@ func TestLoadConfigFromThemeDir(t *testing.T) {
|
||||
theme = "test-theme"
|
||||
|
||||
[params]
|
||||
m1 = "mv1"
|
||||
m1 = "mv1"
|
||||
`
|
||||
|
||||
themeConfig := `
|
||||
[params]
|
||||
t1 = "tv1"
|
||||
t1 = "tv1"
|
||||
t2 = "tv2"
|
||||
`
|
||||
|
||||
@@ -854,6 +853,7 @@ Home.
|
||||
}
|
||||
|
||||
func TestConfigParamSetOnLanguageLevel(t *testing.T) {
|
||||
t.Skip("this has correctly started to fail now.")
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
@@ -884,9 +884,9 @@ ThisIsAParam: {{ site.Params.thisIsAParam }}
|
||||
).BuildE()
|
||||
|
||||
b.Assert(err, qt.IsNil)
|
||||
b.AssertFileContent("public/index.html", `
|
||||
b.AssertFileContent("public/index.html", `
|
||||
MyParam: enParamValue
|
||||
ThisIsAParam: thisIsAParamValue
|
||||
ThisIsAParam: thisIsAParamValue
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -918,7 +918,7 @@ title: "My Swedish Section"
|
||||
-- layouts/index.html --
|
||||
LanguageCode: {{ eq site.LanguageCode site.Language.LanguageCode }}|{{ site.Language.LanguageCode }}|
|
||||
{{ range $i, $e := (slice site .Site) }}
|
||||
{{ $i }}|AllPages: {{ len .AllPages }}|Sections: {{ if .Sections }}true{{ end }}| Author: {{ .Authors }}|BuildDrafts: {{ .BuildDrafts }}|IsMultiLingual: {{ .IsMultiLingual }}|Param: {{ .Language.Params.myparam }}|Language string: {{ .Language }}|Languages: {{ .Languages }}
|
||||
{{ $i }}|AllPages: {{ len .AllPages }}|Sections: {{ if .Sections }}true{{ end }}| Author: {{ .Authors }}|BuildDrafts: {{ .BuildDrafts }}|IsMultilingual: {{ .IsMultiLingual }}|Param: {{ .Language.Params.myparam }}|Language string: {{ .Language }}|Languages: {{ .Languages }}
|
||||
{{ end }}
|
||||
|
||||
|
||||
@@ -938,9 +938,9 @@ LanguageCode: {{ eq site.LanguageCode site.Language.LanguageCode }}|{{ site.Lang
|
||||
b.AssertFileContent("public/index.html", `
|
||||
AllPages: 4|
|
||||
Sections: true|
|
||||
Param: enParamValue
|
||||
Param: enParamValue
|
||||
IsMultiLingual: true
|
||||
Param: enParamValue
|
||||
Param: enParamValue
|
||||
IsMultilingual: true
|
||||
LanguageCode: true|en-US|
|
||||
`)
|
||||
|
||||
@@ -1061,7 +1061,7 @@ Home
|
||||
).BuildE()
|
||||
|
||||
b.Assert(err, qt.IsNil)
|
||||
b.AssertFileContent("public/index.html", `
|
||||
b.AssertFileContent("public/index.html", `
|
||||
Home
|
||||
`)
|
||||
|
||||
@@ -1094,7 +1094,7 @@ HTML.
|
||||
HTACCESS.
|
||||
|
||||
|
||||
|
||||
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
@@ -1110,7 +1110,7 @@ languageCode = "en-US"
|
||||
-- layouts/index.html --
|
||||
LanguageCode: {{ .Site.LanguageCode }}|{{ site.Language.LanguageCode }}|
|
||||
|
||||
|
||||
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
@@ -1136,7 +1136,7 @@ suffixes = ["bar"]
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
|
||||
|
||||
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
@@ -1163,8 +1163,8 @@ func TestConfigMiscPanics(t *testing.T) {
|
||||
params:
|
||||
-- layouts/index.html --
|
||||
Foo: {{ site.Params.foo }}|
|
||||
|
||||
|
||||
|
||||
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
@@ -1187,8 +1187,8 @@ defaultContentLanguage = "en"
|
||||
weight = 1
|
||||
-- layouts/index.html --
|
||||
Foo: {{ site.Params.foo }}|
|
||||
|
||||
|
||||
|
||||
|
||||
`
|
||||
b, err := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
@@ -1214,8 +1214,8 @@ languageCode = "en"
|
||||
languageName = "English"
|
||||
weight = 1
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
`
|
||||
b, err := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
@@ -1240,7 +1240,7 @@ contentDir = "mycontent"
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
|
||||
|
||||
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
@@ -1342,7 +1342,7 @@ disabled = true
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
|
||||
|
||||
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
@@ -1437,7 +1437,7 @@ home = ["html"]
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "page", "section"]
|
||||
|
||||
|
||||
`
|
||||
|
||||
runVariant(t, files, nil)
|
||||
@@ -1538,3 +1538,22 @@ List.
|
||||
b.AssertLogContains("WARN DEPRECATED: Kind \"taxonomyterm\" used in disableKinds is deprecated, use \"taxonomy\" instead.\n")
|
||||
b.AssertLogContains("WARN DEPRECATED: Kind \"taxonomyterm\" used in outputs configuration is deprecated, use \"taxonomy\" instead.\n")
|
||||
}
|
||||
|
||||
func TestDisableKindsIssue12144(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ["page"]
|
||||
defaultContentLanguage = "pt-br"
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
-- content/custom/index.pt-br.md --
|
||||
---
|
||||
title: "P1 pt"
|
||||
---
|
||||
-- content/custom/index.en-us.md --
|
||||
---
|
||||
title: "P1 us"
|
||||
---
|
||||
`
|
||||
Test(t, files)
|
||||
}
|
||||
|
||||
+10
-5
@@ -93,8 +93,8 @@ func (r *resourceSource) GetIdentity() identity.Identity {
|
||||
return r.path
|
||||
}
|
||||
|
||||
func (r *resourceSource) ForEeachIdentity(f func(identity.Identity) bool) {
|
||||
f(r.GetIdentity())
|
||||
func (r *resourceSource) ForEeachIdentity(f func(identity.Identity) bool) bool {
|
||||
return f(r.GetIdentity())
|
||||
}
|
||||
|
||||
func (r *resourceSource) Path() string {
|
||||
@@ -142,14 +142,15 @@ func (n resourceSources) GetIdentity() identity.Identity {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n resourceSources) ForEeachIdentity(f func(identity.Identity) bool) {
|
||||
func (n resourceSources) ForEeachIdentity(f func(identity.Identity) bool) bool {
|
||||
for _, r := range n {
|
||||
if r != nil {
|
||||
if f(r.GetIdentity()) {
|
||||
return
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (cfg contentMapConfig) getTaxonomyConfig(s string) (v viewName) {
|
||||
@@ -180,7 +181,7 @@ func (m *pageMap) AddFi(fi hugofs.FileMetaInfo) error {
|
||||
|
||||
var rs *resourceSource
|
||||
if pi.IsContent() {
|
||||
// Create the page now as we need it at assemembly time.
|
||||
// Create the page now as we need it at assembly time.
|
||||
// The other resources are created if needed.
|
||||
pageResource, pi, err := m.s.h.newPage(
|
||||
&pageMeta{
|
||||
@@ -192,6 +193,10 @@ func (m *pageMap) AddFi(fi hugofs.FileMetaInfo) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if pageResource == nil {
|
||||
// Disabled page.
|
||||
return nil
|
||||
}
|
||||
key = pi.Base()
|
||||
|
||||
rs = &resourceSource{r: pageResource}
|
||||
|
||||
+158
-87
@@ -93,7 +93,8 @@ type pageMap struct {
|
||||
// Used for simple page lookups by name, e.g. "mypage.md" or "mypage".
|
||||
pageReverseIndex *contentTreeReverseIndex
|
||||
|
||||
cachePages *dynacache.Partition[string, page.Pages]
|
||||
cachePages1 *dynacache.Partition[string, page.Pages]
|
||||
cachePages2 *dynacache.Partition[string, page.Pages]
|
||||
cacheResources *dynacache.Partition[string, resource.Resources]
|
||||
cacheContentRendered *dynacache.Partition[string, *resources.StaleValue[contentSummary]]
|
||||
cacheContentPlain *dynacache.Partition[string, *resources.StaleValue[contentPlainPlainWords]]
|
||||
@@ -103,16 +104,16 @@ type pageMap struct {
|
||||
}
|
||||
|
||||
// pageTrees holds pages and resources in a tree structure for all sites/languages.
|
||||
// Eeach site gets its own tree set via the Shape method.
|
||||
// Each site gets its own tree set via the Shape method.
|
||||
type pageTrees struct {
|
||||
// This tree contains all Pages.
|
||||
// This include regular pages, sections, taxonimies and so on.
|
||||
// This include regular pages, sections, taxonomies and so on.
|
||||
// Note that all of these trees share the same key structure,
|
||||
// so you can take a leaf Page key and do a prefix search
|
||||
// with key + "/" to get all of its resources.
|
||||
treePages *doctree.NodeShiftTree[contentNodeI]
|
||||
|
||||
// This tree contains Resoures bundled in pages.
|
||||
// This tree contains Resources bundled in pages.
|
||||
treeResources *doctree.NodeShiftTree[contentNodeI]
|
||||
|
||||
// All pages and resources.
|
||||
@@ -125,39 +126,53 @@ type pageTrees struct {
|
||||
resourceTrees doctree.MutableTrees
|
||||
}
|
||||
|
||||
// collectIdentities collects all identities from in all trees matching the given key.
|
||||
// This will at most match in one tree, but may give identies from multiple dimensions (e.g. language).
|
||||
func (t *pageTrees) collectIdentities(p *paths.Path) []identity.Identity {
|
||||
ids := t.collectIdentitiesFor(p.Base())
|
||||
// collectAndMarkStaleIdentities collects all identities from in all trees matching the given key.
|
||||
// We currently re-read all page/resources for all languages that share the same path,
|
||||
// so we mark all entries as stale (which will trigger cache invalidation), then
|
||||
// return the first.
|
||||
func (t *pageTrees) collectAndMarkStaleIdentities(p *paths.Path) []identity.Identity {
|
||||
key := p.Base()
|
||||
var ids []identity.Identity
|
||||
// We need only one identity sample per dimension.
|
||||
nCount := 0
|
||||
cb := func(n contentNodeI) bool {
|
||||
if n == nil {
|
||||
return false
|
||||
}
|
||||
n.MarkStale()
|
||||
if nCount > 0 {
|
||||
return true
|
||||
}
|
||||
nCount++
|
||||
n.ForEeachIdentity(func(id identity.Identity) bool {
|
||||
ids = append(ids, id)
|
||||
return false
|
||||
})
|
||||
|
||||
return false
|
||||
}
|
||||
tree := t.treePages
|
||||
nCount = 0
|
||||
tree.ForEeachInDimension(key, doctree.DimensionLanguage.Index(),
|
||||
cb,
|
||||
)
|
||||
|
||||
tree = t.treeResources
|
||||
nCount = 0
|
||||
tree.ForEeachInDimension(key, doctree.DimensionLanguage.Index(),
|
||||
cb,
|
||||
)
|
||||
|
||||
if p.Component() == files.ComponentFolderContent {
|
||||
// It may also be a bundled content resource.
|
||||
if n := t.treeResources.Get(p.ForBundleType(paths.PathTypeContentResource).Base()); n != nil {
|
||||
n.ForEeachIdentity(func(id identity.Identity) bool {
|
||||
ids = append(ids, id)
|
||||
return false
|
||||
})
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
key := p.ForBundleType(paths.PathTypeContentResource).Base()
|
||||
tree = t.treeResources
|
||||
nCount = 0
|
||||
tree.ForEeachInDimension(key, doctree.DimensionLanguage.Index(),
|
||||
cb,
|
||||
)
|
||||
|
||||
func (t *pageTrees) collectIdentitiesFor(key string) []identity.Identity {
|
||||
var ids []identity.Identity
|
||||
if n := t.treePages.Get(key); n != nil {
|
||||
n.ForEeachIdentity(func(id identity.Identity) bool {
|
||||
ids = append(ids, id)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
if n := t.treeResources.Get(key); n != nil {
|
||||
n.ForEeachIdentity(func(id identity.Identity) bool {
|
||||
ids = append(ids, id)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
return ids
|
||||
}
|
||||
|
||||
@@ -177,7 +192,7 @@ func (t *pageTrees) collectIdentitiesSurroundingIn(key string, maxSamples int, t
|
||||
level := strings.Count(prefix, "/")
|
||||
tree.WalkPrefixRaw(prefix, func(s string, n contentNodeI) bool {
|
||||
if level != strings.Count(s, "/") {
|
||||
return true
|
||||
return false
|
||||
}
|
||||
n.ForEeachIdentity(func(id identity.Identity) bool {
|
||||
ids = append(ids, id)
|
||||
@@ -207,10 +222,22 @@ 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.createMutableTrees()
|
||||
|
||||
return &t
|
||||
}
|
||||
|
||||
func (t *pageTrees) createMutableTrees() {
|
||||
t.treePagesResources = doctree.WalkableTrees[contentNodeI]{
|
||||
t.treePages,
|
||||
t.treeResources,
|
||||
}
|
||||
|
||||
t.resourceTrees = doctree.MutableTrees{
|
||||
t.treeResources,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
_ resource.Identifier = pageMapQueryPagesInSection{}
|
||||
_ resource.Identifier = pageMapQueryPagesBelowPath{}
|
||||
@@ -298,15 +325,19 @@ func (m *pageMap) forEeachPageIncludingBundledPages(include predicate.P[*pageSta
|
||||
}
|
||||
|
||||
func (m *pageMap) getOrCreatePagesFromCache(
|
||||
cache *dynacache.Partition[string, page.Pages],
|
||||
key string, create func(string) (page.Pages, error),
|
||||
) (page.Pages, error) {
|
||||
return m.cachePages.GetOrCreate(key, create)
|
||||
if cache == nil {
|
||||
cache = m.cachePages1
|
||||
}
|
||||
return cache.GetOrCreate(key, create)
|
||||
}
|
||||
|
||||
func (m *pageMap) getPagesInSection(q pageMapQueryPagesInSection) page.Pages {
|
||||
cacheKey := q.Key()
|
||||
|
||||
pages, err := m.getOrCreatePagesFromCache(cacheKey, func(string) (page.Pages, error) {
|
||||
pages, err := m.getOrCreatePagesFromCache(nil, cacheKey, func(string) (page.Pages, error) {
|
||||
prefix := paths.AddTrailingSlash(q.Path)
|
||||
|
||||
var (
|
||||
@@ -322,31 +353,28 @@ func (m *pageMap) getPagesInSection(q pageMapQueryPagesInSection) page.Pages {
|
||||
w := &doctree.NodeShiftTreeWalker[contentNodeI]{
|
||||
Tree: m.treePages,
|
||||
Prefix: prefix,
|
||||
Handle: func(key string, n contentNodeI, match doctree.DimensionFlag) (bool, error) {
|
||||
if q.Recursive {
|
||||
if p, ok := n.(*pageState); ok && include(p) {
|
||||
pas = append(pas, p)
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// We store both leafs and branches in the same tree, so for non-recursive walks,
|
||||
// we need to walk until the end, but can skip
|
||||
// any not belonging to child branches.
|
||||
if otherBranch != "" && strings.HasPrefix(key, otherBranch) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
w.Handle = func(key string, n contentNodeI, match doctree.DimensionFlag) (bool, error) {
|
||||
if q.Recursive {
|
||||
if p, ok := n.(*pageState); ok && include(p) {
|
||||
pas = append(pas, p)
|
||||
}
|
||||
|
||||
if n.isContentNodeBranch() {
|
||||
otherBranch = key + "/"
|
||||
}
|
||||
|
||||
return false, nil
|
||||
},
|
||||
}
|
||||
|
||||
if p, ok := n.(*pageState); ok && include(p) {
|
||||
pas = append(pas, p)
|
||||
}
|
||||
|
||||
if n.isContentNodeBranch() {
|
||||
currentBranch := key + "/"
|
||||
if otherBranch == "" || otherBranch != currentBranch {
|
||||
w.SkipPrefix(currentBranch)
|
||||
}
|
||||
otherBranch = currentBranch
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
err := w.Walk(context.Background())
|
||||
@@ -374,7 +402,7 @@ func (m *pageMap) getPagesInSection(q pageMapQueryPagesInSection) page.Pages {
|
||||
func (m *pageMap) getPagesWithTerm(q pageMapQueryPagesBelowPath) page.Pages {
|
||||
key := q.Key()
|
||||
|
||||
v, err := m.cachePages.GetOrCreate(key, func(string) (page.Pages, error) {
|
||||
v, err := m.cachePages1.GetOrCreate(key, func(string) (page.Pages, error) {
|
||||
var pas page.Pages
|
||||
include := q.Include
|
||||
if include == nil {
|
||||
@@ -411,7 +439,7 @@ func (m *pageMap) getPagesWithTerm(q pageMapQueryPagesBelowPath) page.Pages {
|
||||
func (m *pageMap) getTermsForPageInTaxonomy(path, taxonomy string) page.Pages {
|
||||
prefix := paths.AddLeadingSlash(taxonomy)
|
||||
|
||||
v, err := m.cachePages.GetOrCreate(prefix+path, func(string) (page.Pages, error) {
|
||||
v, err := m.cachePages1.GetOrCreate(prefix+path, func(string) (page.Pages, error) {
|
||||
var pas page.Pages
|
||||
|
||||
err := m.treeTaxonomyEntries.WalkPrefix(
|
||||
@@ -461,12 +489,17 @@ func (m *pageMap) forEachResourceInPage(
|
||||
|
||||
rw.Handle = func(resourceKey string, n contentNodeI, match doctree.DimensionFlag) (bool, error) {
|
||||
if isBranch {
|
||||
ownerKey, _ := m.treePages.LongestPrefixAll(resourceKey)
|
||||
// A resourceKey always represents a filename with extension.
|
||||
// A page key points to the logical path of a page, which when sourced from the filesystem
|
||||
// may represent a directory (bundles) or a single content file (e.g. p1.md).
|
||||
// So, to avoid any overlapping ambiguity, we start looking from the owning directory.
|
||||
ownerKey, _ := m.treePages.LongestPrefixAll(path.Dir(resourceKey))
|
||||
if ownerKey != keyPage {
|
||||
// Stop walking downwards, someone else owns this resource.
|
||||
rw.SkipPrefix(ownerKey + "/")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
}
|
||||
return handle(resourceKey, n, match)
|
||||
}
|
||||
@@ -516,7 +549,7 @@ func (m *pageMap) getOrCreateResourcesForPage(ps *pageState) resource.Resources
|
||||
for _, r := range res2 {
|
||||
var found bool
|
||||
for _, r2 := range res {
|
||||
if r2.Name() == r.Name() {
|
||||
if r2.(resource.NameNormalizedProvider).NameNormalized() == r.(resource.NameNormalizedProvider).NameNormalized() {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
@@ -605,12 +638,15 @@ func (n contentNodeIs) GetIdentity() identity.Identity {
|
||||
return n[0].GetIdentity()
|
||||
}
|
||||
|
||||
func (n contentNodeIs) ForEeachIdentity(f func(identity.Identity) bool) {
|
||||
func (n contentNodeIs) ForEeachIdentity(f func(identity.Identity) bool) bool {
|
||||
for _, nn := range n {
|
||||
if nn != nil {
|
||||
nn.ForEeachIdentity(f)
|
||||
if nn.ForEeachIdentity(f) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (n contentNodeIs) resetBuildState() {
|
||||
@@ -623,9 +659,7 @@ func (n contentNodeIs) resetBuildState() {
|
||||
|
||||
func (n contentNodeIs) MarkStale() {
|
||||
for _, nn := range n {
|
||||
if nn != nil {
|
||||
nn.MarkStale()
|
||||
}
|
||||
resource.MarkStale(nn)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -661,9 +695,15 @@ func (s *contentNodeShifter) Delete(n contentNodeI, dimension doctree.Dimension)
|
||||
}
|
||||
return wasDeleted, isEmpty
|
||||
case *resourceSource:
|
||||
if lidx != v.LangIndex() {
|
||||
return false, false
|
||||
}
|
||||
resource.MarkStale(v)
|
||||
return true, true
|
||||
case *pageState:
|
||||
if lidx != v.s.languagei {
|
||||
return false, false
|
||||
}
|
||||
resource.MarkStale(v)
|
||||
return true, true
|
||||
default:
|
||||
@@ -796,6 +836,7 @@ func (s *contentNodeShifter) Insert(old, new contentNodeI) contentNodeI {
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("unknown type %T", new))
|
||||
}
|
||||
resource.MarkStale(vv[newp.s.languagei])
|
||||
vv[newp.s.languagei] = new
|
||||
return vv
|
||||
case *resourceSource:
|
||||
@@ -815,6 +856,7 @@ func (s *contentNodeShifter) Insert(old, new contentNodeI) contentNodeI {
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("unknown type %T", new))
|
||||
}
|
||||
resource.MarkStale(vv[newp.LangIndex()])
|
||||
vv[newp.LangIndex()] = newp
|
||||
return vv
|
||||
default:
|
||||
@@ -829,7 +871,8 @@ func newPageMap(i int, s *Site, mcache *dynacache.Cache, pageTrees *pageTrees) *
|
||||
|
||||
m = &pageMap{
|
||||
pageTrees: pageTrees.Shape(0, i),
|
||||
cachePages: dynacache.GetOrCreatePartition[string, page.Pages](mcache, fmt.Sprintf("/pags/%d", i), dynacache.OptionsPartition{Weight: 10, ClearWhen: dynacache.ClearOnRebuild}),
|
||||
cachePages1: dynacache.GetOrCreatePartition[string, page.Pages](mcache, fmt.Sprintf("/pag1/%d", i), dynacache.OptionsPartition{Weight: 10, ClearWhen: dynacache.ClearOnRebuild}),
|
||||
cachePages2: dynacache.GetOrCreatePartition[string, page.Pages](mcache, fmt.Sprintf("/pag2/%d", i), dynacache.OptionsPartition{Weight: 10, ClearWhen: dynacache.ClearOnRebuild}),
|
||||
cacheResources: dynacache.GetOrCreatePartition[string, resource.Resources](mcache, fmt.Sprintf("/ress/%d", i), dynacache.OptionsPartition{Weight: 60, ClearWhen: dynacache.ClearOnRebuild}),
|
||||
cacheContentRendered: dynacache.GetOrCreatePartition[string, *resources.StaleValue[contentSummary]](mcache, fmt.Sprintf("/cont/ren/%d", i), dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
|
||||
cacheContentPlain: dynacache.GetOrCreatePartition[string, *resources.StaleValue[contentPlainPlainWords]](mcache, fmt.Sprintf("/cont/pla/%d", i), dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
|
||||
@@ -994,7 +1037,7 @@ func (h *HugoSites) resolveAndClearStateForIdentities(
|
||||
seen := map[string]bool{
|
||||
change.IdentifierBase(): true,
|
||||
}
|
||||
// Print the top level dependenies.
|
||||
// Print the top level dependencies.
|
||||
identity.WalkIdentitiesDeep(change, func(level int, id identity.Identity) bool {
|
||||
if level > 1 {
|
||||
return true
|
||||
@@ -1011,8 +1054,12 @@ func (h *HugoSites) resolveAndClearStateForIdentities(
|
||||
)
|
||||
|
||||
for _, id := range changes {
|
||||
if staler, ok := id.(resource.Staler); ok {
|
||||
h.Log.Trace(logg.StringFunc(func() string { return fmt.Sprintf("Marking stale: %s (%T)\n", id, id) }))
|
||||
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())
|
||||
}
|
||||
h.Log.Trace(logg.StringFunc(func() string { return fmt.Sprintf("Marking stale: %s (%T)%s\n", id, id, msgDetail) }))
|
||||
staler.MarkStale()
|
||||
}
|
||||
}
|
||||
@@ -1045,7 +1092,7 @@ func (h *HugoSites) resolveAndClearStateForIdentities(
|
||||
}
|
||||
}
|
||||
|
||||
// Drain the the cache eviction stack.
|
||||
// Drain the cache eviction stack.
|
||||
evicted := h.Deps.MemCache.DrainEvictedIdentities()
|
||||
if len(evicted) < 200 {
|
||||
changes = append(changes, evicted...)
|
||||
@@ -1151,7 +1198,7 @@ func (h *HugoSites) resolveAndResetDependententPageOutputs(ctx context.Context,
|
||||
// First check the top level dependency manager.
|
||||
for _, id := range changes {
|
||||
checkedCounter.Add(1)
|
||||
if r := depsFinder.Contains(id, p.dependencyManager, 100); r > identity.FinderFoundOneOfManyRepetition {
|
||||
if r := depsFinder.Contains(id, p.dependencyManager, 2); r > identity.FinderFoundOneOfManyRepetition {
|
||||
for _, po := range p.pageOutputs {
|
||||
resetPo(po, r)
|
||||
}
|
||||
@@ -1167,7 +1214,7 @@ func (h *HugoSites) resolveAndResetDependententPageOutputs(ctx context.Context,
|
||||
}
|
||||
for _, id := range changes {
|
||||
checkedCounter.Add(1)
|
||||
if r := depsFinder.Contains(id, po.dependencyManagerOutput, 2); r > identity.FinderFoundOneOfManyRepetition {
|
||||
if r := depsFinder.Contains(id, po.dependencyManagerOutput, 50); r > identity.FinderFoundOneOfManyRepetition {
|
||||
resetPo(po, r)
|
||||
continue OUTPUTS
|
||||
}
|
||||
@@ -1393,8 +1440,10 @@ func (sa *sitePagesAssembler) applyAggregatesToTaxonomiesAndTerms() error {
|
||||
if err := p.setMetaPost(cascade); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := sa.pageMap.treeTaxonomyEntries.WalkPrefix(
|
||||
if !p.s.shouldBuild(p) {
|
||||
sa.pageMap.treePages.Delete(s)
|
||||
sa.pageMap.treeTaxonomyEntries.DeletePrefix(paths.AddTrailingSlash(s))
|
||||
} else if err := sa.pageMap.treeTaxonomyEntries.WalkPrefix(
|
||||
doctree.LockTypeRead,
|
||||
paths.AddTrailingSlash(s),
|
||||
func(ss string, wn *weightedContentNode) (bool, error) {
|
||||
@@ -1462,13 +1511,6 @@ func (sa *sitePagesAssembler) assembleTermsAndTranslations() error {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// This is a little out of place, but is conveniently put here.
|
||||
// Check if translationKey is set by user.
|
||||
// This is to support the manual way of setting the translationKey in front matter.
|
||||
if ps.m.pageConfig.TranslationKey != "" {
|
||||
sa.s.h.translationKeyPages.Append(ps.m.pageConfig.TranslationKey, ps)
|
||||
}
|
||||
|
||||
if sa.pageMap.cfg.taxonomyTermDisabled {
|
||||
return false, nil
|
||||
}
|
||||
@@ -1511,8 +1553,10 @@ func (sa *sitePagesAssembler) assembleTermsAndTranslations() error {
|
||||
}
|
||||
pages.InsertIntoValuesDimension(pi.Base(), n)
|
||||
term = pages.Get(pi.Base())
|
||||
} else if term.(*pageState).m.term != v {
|
||||
term.(*pageState).m.term = v
|
||||
} else {
|
||||
m := term.(*pageState).m
|
||||
m.term = v
|
||||
m.singular = viewName.singular
|
||||
}
|
||||
|
||||
if s == "" {
|
||||
@@ -1547,6 +1591,13 @@ func (sa *sitePagesAssembler) assembleResources() error {
|
||||
Handle: func(s string, n contentNodeI, match doctree.DimensionFlag) (bool, error) {
|
||||
ps := n.(*pageState)
|
||||
|
||||
// This is a little out of place, but is conveniently put here.
|
||||
// Check if translationKey is set by user.
|
||||
// This is to support the manual way of setting the translationKey in front matter.
|
||||
if ps.m.pageConfig.TranslationKey != "" {
|
||||
sa.s.h.translationKeyPages.Append(ps.m.pageConfig.TranslationKey, ps)
|
||||
}
|
||||
|
||||
// Prepare resources for this page.
|
||||
ps.shiftToOutputFormat(true, 0)
|
||||
targetPaths := ps.targetPaths()
|
||||
@@ -1592,7 +1643,7 @@ func (sa *sitePagesAssembler) assembleResources() error {
|
||||
TargetBasePaths: targetBasePaths,
|
||||
BasePathRelPermalink: targetPaths.SubResourceBaseLink,
|
||||
BasePathTargetPath: baseTarget,
|
||||
Name: relPath,
|
||||
NameNormalized: relPath,
|
||||
NameOriginal: relPathOriginal,
|
||||
LazyPublish: !ps.m.pageConfig.Build.PublishResources,
|
||||
}
|
||||
@@ -1638,6 +1689,11 @@ func (sa *sitePagesAssembler) assemblePagesStep2() error {
|
||||
if err := sa.applyAggregatesToTaxonomiesAndTerms(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sa *sitePagesAssembler) assemblePagesStepFinal() error {
|
||||
if err := sa.assembleResources(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1671,6 +1727,10 @@ func (sa *sitePagesAssembler) removeShouldNotBuild() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sa.pageMap.DeletePageAndResourcesBelow(keys...)
|
||||
|
||||
return nil
|
||||
@@ -1720,7 +1780,7 @@ func (sa *sitePagesAssembler) addStandalonePages() error {
|
||||
|
||||
if s.conf.EnableRobotsTXT {
|
||||
if m.i == 0 || s.Conf.IsMultihost() {
|
||||
addStandalone("/robots", kinds.KindRobotsTXT, output.RobotsTxtFormat)
|
||||
addStandalone("/_robots", kinds.KindRobotsTXT, output.RobotsTxtFormat)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1733,11 +1793,11 @@ func (sa *sitePagesAssembler) addStandalonePages() error {
|
||||
}
|
||||
|
||||
if sitemapEnabled {
|
||||
addStandalone("/sitemap", kinds.KindSitemap, output.SitemapFormat)
|
||||
skipSitemapIndex := s.Conf.IsMultihost() || !(s.Conf.DefaultContentLanguageInSubdir() || s.Conf.IsMultiLingual())
|
||||
addStandalone("/_sitemap", kinds.KindSitemap, output.SitemapFormat)
|
||||
skipSitemapIndex := s.Conf.IsMultihost() || !(s.Conf.DefaultContentLanguageInSubdir() || s.Conf.IsMultilingual())
|
||||
|
||||
if !skipSitemapIndex {
|
||||
addStandalone("/sitemapindex", kinds.KindSitemapIndex, output.SitemapIndexFormat)
|
||||
addStandalone("/_sitemapindex", kinds.KindSitemapIndex, output.SitemapIndexFormat)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1770,6 +1830,14 @@ func (sa *sitePagesAssembler) addMissingRootSections() error {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
switch ps.Kind() {
|
||||
case kinds.KindPage, kinds.KindSection:
|
||||
// OK
|
||||
default:
|
||||
// Skip taxonomy nodes etc.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
p := ps.m.pathInfo
|
||||
section := p.Section()
|
||||
if section == "" || seen[section] {
|
||||
@@ -1880,6 +1948,9 @@ func (m *pageMap) CreateSiteTaxonomies(ctx context.Context) error {
|
||||
|
||||
switch p.Kind() {
|
||||
case kinds.KindTerm:
|
||||
if !p.m.shouldList(true) {
|
||||
return false, nil
|
||||
}
|
||||
taxonomy := m.s.taxonomies[viewName.plural]
|
||||
if taxonomy == nil {
|
||||
return true, fmt.Errorf("missing taxonomy: %s", viewName.plural)
|
||||
|
||||
@@ -160,7 +160,7 @@ Draft5: {{ if (.Site.GetPage "blog/draftsection/sub/page") }}FOUND{{ end }}|
|
||||
Sections: /blog/|/docs/|/overlap/|/overlap2/|:END
|
||||
Categories: /categories/funny/; Funny; 12|/categories/sad/; Sad; 2|:END
|
||||
Category Terms: taxonomy: /categories/funny/; Funny; 12|/categories/sad/; Sad; 2|:END
|
||||
Category Funny: term; Funny: /blog/subsection/page4/;|/blog/page3/;|/blog/subsection/;|/blog/page2/;|/blog/page1/;|/blog/subsection/page5/;|/docs/page6/;|/blog/bundle/;|/blog/draftsection/page/;|/blog/draftsection/sub/;|/blog/draftsection/sub/page/;|/overlap2/;|:END
|
||||
Category Funny: term; funny: /blog/subsection/page4/;|/blog/page3/;|/blog/subsection/;|/blog/page2/;|/blog/page1/;|/blog/subsection/page5/;|/docs/page6/;|/blog/bundle/;|/blog/draftsection/page/;|/blog/draftsection/sub/;|/blog/draftsection/sub/page/;|/overlap2/;|:END
|
||||
Pag Num Pages: 9
|
||||
Pag Blog Num Pages: 4
|
||||
Blog Num RegularPages: 4
|
||||
@@ -326,3 +326,35 @@ R: {{ with $r }}{{ .Content }}{{ end }}|Len: {{ len $bundle.Resources }}|$
|
||||
b.AssertFileContent("public/index.html", "R: Data 1.txt|", "Len: 1|")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBundleResourcesNoPublishedIssue12198(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','rss','sitemap','taxonomy','term']
|
||||
-- content/s1/p1.md --
|
||||
---
|
||||
title: p1
|
||||
---
|
||||
-- content/s1/foo.txt --
|
||||
foo.txt
|
||||
-- content/s1/p1.txt --
|
||||
p1.txt
|
||||
-- content/s1/p1-foo.txt --
|
||||
p1-foo.txt
|
||||
-- layouts/_default/list.html --
|
||||
{{.Title }}|
|
||||
-- layouts/_default/single.html --
|
||||
{{.Title }}|
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
b.Build()
|
||||
|
||||
b.AssertFileExists("public/s1/index.html", true)
|
||||
b.AssertFileExists("public/s1/foo.txt", true)
|
||||
b.AssertFileExists("public/s1/p1.txt", true) // failing test
|
||||
b.AssertFileExists("public/s1/p1-foo.txt", true) // failing test
|
||||
b.AssertFileExists("public/s1/p1/index.html", true)
|
||||
}
|
||||
|
||||
@@ -64,3 +64,21 @@ v1: {{ site.Data.MyFolder.MyData.v1 }}|
|
||||
|
||||
b.AssertFileContent("public/index.html", "v1: my_v1|")
|
||||
}
|
||||
|
||||
// Issue #12133
|
||||
func TestDataNoAssets(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
|
||||
-- assets/data/foo.toml --
|
||||
content = "I am assets/data/foo.toml"
|
||||
-- layouts/index.html --
|
||||
|{{ site.Data.foo.content }}|
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/index.html", "||")
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ ExpiryDate: 2099-07-13 15:28:01 +0000 UTC`
|
||||
// have date literals.
|
||||
|
||||
// YAML
|
||||
// Note: This is with go-yaml v2, I suspect v3 will fail with the unquouted values.
|
||||
// Note: This is with go-yaml v2, I suspect v3 will fail with the unquoted values.
|
||||
b.AssertFileContent("public/en/short-date-yaml-unqouted/index.html", expectShortDateEn)
|
||||
b.AssertFileContent("public/nn/short-date-yaml-unqouted/index.html", expectShortDateNn)
|
||||
b.AssertFileContent("public/en/short-date-yaml-qouted/index.html", expectShortDateEn)
|
||||
|
||||
@@ -67,7 +67,7 @@ package main
|
||||
|
||||
## YouTube
|
||||
|
||||
{{< youtube PArFPgHrNZM >}}
|
||||
{{< youtube 0RKpf3rK57I >}}
|
||||
|
||||
## Param
|
||||
|
||||
@@ -83,7 +83,7 @@ Content: {{ .Content }}|
|
||||
https://gist.github.com/spf13/7896402.js
|
||||
<span style="color:#a6e22e">main</span></span>
|
||||
https://t.co/X94FmYDEZJ
|
||||
https://www.youtube.com/embed/PArFPgHrNZM
|
||||
https://www.youtube.com/embed/0RKpf3rK57I
|
||||
Foo: bar
|
||||
|
||||
|
||||
|
||||
@@ -15,8 +15,6 @@ package hugolib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestInternalTemplatesImage(t *testing.T) {
|
||||
@@ -62,78 +60,41 @@ title: My Site
|
||||
b.Build(BuildCfg{})
|
||||
|
||||
b.AssertFileContent("public/mybundle/index.html", `
|
||||
<meta name="twitter:image" content="https://example.org/mybundle/featured-sunset.jpg" />
|
||||
<meta name="twitter:title" content="My Bundle"/>
|
||||
<meta property="og:title" content="My Bundle" />
|
||||
<meta property="og:url" content="https://example.org/mybundle/" />
|
||||
<meta property="og:image" content="https://example.org/mybundle/featured-sunset.jpg" />
|
||||
<meta property="article:published_time" content="2021-02-26T18:02:00-01:00" />
|
||||
<meta property="article:modified_time" content="2021-05-22T19:25:00-01:00" />
|
||||
<meta name="twitter:image" content="https://example.org/mybundle/featured-sunset.jpg">
|
||||
<meta name="twitter:title" content="My Bundle">
|
||||
<meta property="og:title" content="My Bundle">
|
||||
<meta property="og:url" content="https://example.org/mybundle/">
|
||||
<meta property="og:image" content="https://example.org/mybundle/featured-sunset.jpg">
|
||||
<meta property="article:published_time" content="2021-02-26T18:02:00-01:00">
|
||||
<meta property="article:modified_time" content="2021-05-22T19:25:00-01:00">
|
||||
<meta itemprop="name" content="My Bundle">
|
||||
<meta itemprop="image" content="https://example.org/mybundle/featured-sunset.jpg" />
|
||||
<meta itemprop="datePublished" content="2021-02-26T18:02:00-01:00" />
|
||||
<meta itemprop="dateModified" content="2021-05-22T19:25:00-01:00" />
|
||||
<meta itemprop="image" content="https://example.org/mybundle/featured-sunset.jpg">
|
||||
<meta itemprop="datePublished" content="2021-02-26T18:02:00-01:00">
|
||||
<meta itemprop="dateModified" content="2021-05-22T19:25:00-01:00">
|
||||
|
||||
`)
|
||||
b.AssertFileContent("public/mypage/index.html", `
|
||||
<meta name="twitter:image" content="https://example.org/pageimg1.jpg" />
|
||||
<meta property="og:image" content="https://example.org/pageimg1.jpg" />
|
||||
<meta property="og:image" content="https://example.org/pageimg2.jpg" />
|
||||
<meta property="og:image" content="https://example.local/logo.png" />
|
||||
<meta property="og:image" content="https://example.org/mypage/sample.jpg" />
|
||||
<meta property="article:published_time" content="2021-02-26T18:02:00+01:00" />
|
||||
<meta property="article:modified_time" content="2021-05-22T19:25:00+01:00" />
|
||||
<meta itemprop="image" content="https://example.org/pageimg1.jpg" />
|
||||
<meta itemprop="image" content="https://example.org/pageimg2.jpg" />
|
||||
<meta itemprop="image" content="https://example.local/logo.png" />
|
||||
<meta itemprop="image" content="https://example.org/mypage/sample.jpg" />
|
||||
<meta itemprop="datePublished" content="2021-02-26T18:02:00+01:00" />
|
||||
<meta itemprop="dateModified" content="2021-05-22T19:25:00+01:00" />
|
||||
<meta name="twitter:image" content="https://example.org/pageimg1.jpg">
|
||||
<meta property="og:image" content="https://example.org/pageimg1.jpg">
|
||||
<meta property="og:image" content="https://example.org/pageimg2.jpg">
|
||||
<meta property="og:image" content="https://example.local/logo.png">
|
||||
<meta property="og:image" content="https://example.org/mypage/sample.jpg">
|
||||
<meta property="article:published_time" content="2021-02-26T18:02:00+01:00">
|
||||
<meta property="article:modified_time" content="2021-05-22T19:25:00+01:00">
|
||||
<meta itemprop="image" content="https://example.org/pageimg1.jpg">
|
||||
<meta itemprop="image" content="https://example.org/pageimg2.jpg">
|
||||
<meta itemprop="image" content="https://example.local/logo.png">
|
||||
<meta itemprop="image" content="https://example.org/mypage/sample.jpg">
|
||||
<meta itemprop="datePublished" content="2021-02-26T18:02:00+01:00">
|
||||
<meta itemprop="dateModified" content="2021-05-22T19:25:00+01:00">
|
||||
`)
|
||||
b.AssertFileContent("public/mysite/index.html", `
|
||||
<meta name="twitter:image" content="https://example.org/siteimg1.jpg" />
|
||||
<meta property="og:image" content="https://example.org/siteimg1.jpg" />
|
||||
<meta itemprop="image" content="https://example.org/siteimg1.jpg" />
|
||||
<meta name="twitter:image" content="https://example.org/siteimg1.jpg">
|
||||
<meta property="og:image" content="https://example.org/siteimg1.jpg">
|
||||
<meta itemprop="image" content="https://example.org/siteimg1.jpg">
|
||||
`)
|
||||
}
|
||||
|
||||
// Just some simple test of the embedded templates to avoid
|
||||
// https://github.com/gohugoio/hugo/issues/4757 and similar.
|
||||
func TestEmbeddedTemplates(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c := qt.New(t)
|
||||
c.Assert(true, qt.Equals, true)
|
||||
|
||||
home := []string{"index.html", `
|
||||
GA:
|
||||
{{ template "_internal/google_analytics.html" . }}
|
||||
|
||||
GA async:
|
||||
|
||||
{{ template "_internal/google_analytics_async.html" . }}
|
||||
|
||||
Disqus:
|
||||
|
||||
{{ template "_internal/disqus.html" . }}
|
||||
|
||||
`}
|
||||
|
||||
b := newTestSitesBuilder(t)
|
||||
b.WithSimpleConfigFile().WithTemplatesAdded(home...)
|
||||
|
||||
b.Build(BuildCfg{})
|
||||
|
||||
// Gheck GA regular and async
|
||||
b.AssertFileContent("public/index.html",
|
||||
"'anonymizeIp', true",
|
||||
"'script','https://www.google-analytics.com/analytics.js','ga');\n\tga('create', 'UA-ga_id', 'auto')",
|
||||
"<script async src='https://www.google-analytics.com/analytics.js'>")
|
||||
|
||||
// Disqus
|
||||
b.AssertFileContent("public/index.html", "\"disqus_shortname\" + '.disqus.com/embed.js';")
|
||||
}
|
||||
|
||||
func TestEmbeddedPaginationTemplate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2024 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
@@ -265,6 +265,9 @@ type SourceFilesystem struct {
|
||||
// This is a virtual composite filesystem. It expects path relative to a context.
|
||||
Fs afero.Fs
|
||||
|
||||
// The source filesystem (usually the OS filesystem).
|
||||
SourceFs afero.Fs
|
||||
|
||||
// When syncing a source folder to the target (e.g. /public), this may
|
||||
// be set to publish into a subfolder. This is used for static syncing
|
||||
// in multihost mode.
|
||||
@@ -320,10 +323,10 @@ func (s SourceFilesystems) IsContent(filename string) bool {
|
||||
}
|
||||
|
||||
// ResolvePaths resolves the given filename to a list of paths in the filesystems.
|
||||
func (s *SourceFilesystems) ResolvePaths(filename string, checkExists bool) []hugofs.ComponentPath {
|
||||
func (s *SourceFilesystems) ResolvePaths(filename string) []hugofs.ComponentPath {
|
||||
var cpss []hugofs.ComponentPath
|
||||
for _, rfs := range s.RootFss {
|
||||
cps, err := rfs.ReverseLookup(filename, checkExists)
|
||||
cps, err := rfs.ReverseLookup(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -336,7 +339,7 @@ func (s *SourceFilesystems) ResolvePaths(filename string, checkExists bool) []hu
|
||||
// It will return an empty string if the filename is not a member of a static filesystem.
|
||||
func (s SourceFilesystems) MakeStaticPathRelative(filename string) string {
|
||||
for _, staticFs := range s.Static {
|
||||
rel, _ := staticFs.MakePathRelative(filename)
|
||||
rel, _ := staticFs.MakePathRelative(filename, true)
|
||||
if rel != "" {
|
||||
return rel
|
||||
}
|
||||
@@ -345,8 +348,8 @@ func (s SourceFilesystems) MakeStaticPathRelative(filename string) string {
|
||||
}
|
||||
|
||||
// MakePathRelative creates a relative path from the given filename.
|
||||
func (d *SourceFilesystem) MakePathRelative(filename string) (string, bool) {
|
||||
cps, err := d.ReverseLookup(filename)
|
||||
func (d *SourceFilesystem) MakePathRelative(filename string, checkExists bool) (string, bool) {
|
||||
cps, err := d.ReverseLookup(filename, checkExists)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -358,11 +361,21 @@ func (d *SourceFilesystem) MakePathRelative(filename string) (string, bool) {
|
||||
}
|
||||
|
||||
// ReverseLookup returns the component paths for the given filename.
|
||||
func (d *SourceFilesystem) ReverseLookup(filename string) ([]hugofs.ComponentPath, error) {
|
||||
func (d *SourceFilesystem) ReverseLookup(filename string, checkExists bool) ([]hugofs.ComponentPath, error) {
|
||||
var cps []hugofs.ComponentPath
|
||||
hugofs.WalkFilesystems(d.Fs, func(fs afero.Fs) bool {
|
||||
if rfs, ok := fs.(hugofs.ReverseLookupProvder); ok {
|
||||
if c, err := rfs.ReverseLookup(filename, true); err == nil {
|
||||
if c, err := rfs.ReverseLookupComponent(d.Name, filename); err == nil {
|
||||
if checkExists {
|
||||
n := 0
|
||||
for _, cp := range c {
|
||||
if _, err := d.Fs.Stat(filepath.FromSlash(cp.Path)); err == nil {
|
||||
c[n] = cp
|
||||
n++
|
||||
}
|
||||
}
|
||||
c = c[:n]
|
||||
}
|
||||
cps = append(cps, c...)
|
||||
}
|
||||
}
|
||||
@@ -379,11 +392,12 @@ func (d *SourceFilesystem) mounts() []hugofs.FileMetaInfo {
|
||||
if err == nil {
|
||||
m = append(m, mounts...)
|
||||
}
|
||||
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// Filter out any mounts not belonging to this filesystem.
|
||||
// TODO(bep) I think this is superflous.
|
||||
n := 0
|
||||
for _, mm := range m {
|
||||
if mm.Meta().Component == d.Name {
|
||||
@@ -392,6 +406,7 @@ func (d *SourceFilesystem) mounts() []hugofs.FileMetaInfo {
|
||||
}
|
||||
}
|
||||
m = m[:n]
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -428,10 +443,8 @@ func (d *SourceFilesystem) RealDirs(from string) []string {
|
||||
if !m.IsDir() {
|
||||
continue
|
||||
}
|
||||
meta := m.Meta()
|
||||
_, err := d.Fs.Stat(from)
|
||||
if err == nil {
|
||||
dirname := filepath.Join(meta.Filename, from)
|
||||
dirname := filepath.Join(m.Meta().Filename, from)
|
||||
if _, err := d.SourceFs.Stat(dirname); err == nil {
|
||||
dirnames = append(dirnames, dirname)
|
||||
}
|
||||
}
|
||||
@@ -519,8 +532,9 @@ func newSourceFilesystemsBuilder(p *paths.Paths, logger loggers.Logger, b *BaseF
|
||||
|
||||
func (b *sourceFilesystemsBuilder) newSourceFilesystem(name string, fs afero.Fs) *SourceFilesystem {
|
||||
return &SourceFilesystem{
|
||||
Name: name,
|
||||
Fs: fs,
|
||||
Name: name,
|
||||
Fs: fs,
|
||||
SourceFs: b.sourceFs,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -234,6 +234,7 @@ foo
|
||||
b := hugolib.Test(t, files)
|
||||
bfs := b.H.BaseFs
|
||||
watchFilenames := bfs.WatchFilenames()
|
||||
// []string{"/hugo_stats.json", "/content", "/content2", "/themes/t1/layouts", "/themes/t1/layouts/_default", "/themes/t1/static"}
|
||||
b.Assert(watchFilenames, qt.HasLen, 6)
|
||||
}
|
||||
|
||||
@@ -314,7 +315,7 @@ func TestStaticFs(t *testing.T) {
|
||||
checkFileContent(sfs, "f2.txt", c, "Hugo Themes Still Rocks!")
|
||||
}
|
||||
|
||||
func TestStaticFsMultiHost(t *testing.T) {
|
||||
func TestStaticFsMultihost(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
v := config.New()
|
||||
workDir := "mywork"
|
||||
@@ -380,11 +381,11 @@ Main.
|
||||
`
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
rel, found := b.H.BaseFs.Assets.MakePathRelative(filepath.FromSlash("/themes/t1/src/main.js"))
|
||||
rel, found := b.H.BaseFs.Assets.MakePathRelative(filepath.FromSlash("/themes/t1/src/main.js"), true)
|
||||
b.Assert(found, qt.Equals, true)
|
||||
b.Assert(rel, qt.Equals, filepath.FromSlash("foo/bar/main.js"))
|
||||
|
||||
rel, found = b.H.BaseFs.Assets.MakePathRelative(filepath.FromSlash("/bar.txt"))
|
||||
rel, found = b.H.BaseFs.Assets.MakePathRelative(filepath.FromSlash("/bar.txt"), true)
|
||||
b.Assert(found, qt.Equals, true)
|
||||
b.Assert(rel, qt.Equals, filepath.FromSlash("foo/baz.txt"))
|
||||
}
|
||||
@@ -460,7 +461,7 @@ Home.
|
||||
b.AssertFileContent("public/index.html", "Home.")
|
||||
|
||||
stat := func(path string) hugofs.FileMetaInfo {
|
||||
ps, err := b.H.BaseFs.Content.ReverseLookup(filepath.FromSlash(path))
|
||||
ps, err := b.H.BaseFs.Content.ReverseLookup(filepath.FromSlash(path), true)
|
||||
b.Assert(err, qt.IsNil)
|
||||
b.Assert(ps, qt.HasLen, 1)
|
||||
first := ps[0]
|
||||
@@ -478,11 +479,135 @@ Home.
|
||||
_ = stat("blog/b1.md")
|
||||
}
|
||||
|
||||
func TestReverseLookupShouldOnlyConsiderFilesInCurrentComponent(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com/"
|
||||
[module]
|
||||
[[module.mounts]]
|
||||
source = "files/layouts"
|
||||
target = "layouts"
|
||||
[[module.mounts]]
|
||||
source = "files/layouts/assets"
|
||||
target = "assets"
|
||||
-- files/layouts/l1.txt --
|
||||
l1
|
||||
-- files/layouts/assets/l2.txt --
|
||||
l2
|
||||
`
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
assetsFs := b.H.Assets
|
||||
|
||||
for _, checkExists := range []bool{false, true} {
|
||||
cps, err := assetsFs.ReverseLookup(filepath.FromSlash("files/layouts/assets/l2.txt"), checkExists)
|
||||
b.Assert(err, qt.IsNil)
|
||||
b.Assert(cps, qt.HasLen, 1)
|
||||
cps, err = assetsFs.ReverseLookup(filepath.FromSlash("files/layouts/l2.txt"), checkExists)
|
||||
b.Assert(err, qt.IsNil)
|
||||
b.Assert(cps, qt.HasLen, 0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssetsIssue12175(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com/"
|
||||
[module]
|
||||
[[module.mounts]]
|
||||
source = "node_modules/@foo/core/assets"
|
||||
target = "assets"
|
||||
[[module.mounts]]
|
||||
source = "assets"
|
||||
target = "assets"
|
||||
-- node_modules/@foo/core/assets/js/app.js --
|
||||
JS.
|
||||
-- node_modules/@foo/core/assets/scss/app.scss --
|
||||
body { color: red; }
|
||||
-- assets/scss/app.scss --
|
||||
body { color: blue; }
|
||||
-- layouts/index.html --
|
||||
Home.
|
||||
SCSS: {{ with resources.Get "scss/app.scss" }}{{ .RelPermalink }}|{{ .Content }}{{ end }}|
|
||||
# Note that the pattern below will match 2 resources, which doesn't make much sense,
|
||||
# but is how the current (and also < v0.123.0) merge logic works, and for most practical purposes, it doesn't matter.
|
||||
SCSS Match: {{ with resources.Match "**.scss" }}{{ . | len }}|{{ range .}}{{ .RelPermalink }}|{{ end }}{{ end }}|
|
||||
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/index.html", `
|
||||
SCSS: /scss/app.scss|body { color: blue; }|
|
||||
SCSS Match: 2|
|
||||
`)
|
||||
}
|
||||
|
||||
func TestStaticComposite(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ["taxonomy", "term"]
|
||||
[module]
|
||||
[[module.mounts]]
|
||||
source = "myfiles/f1.txt"
|
||||
target = "static/files/f1.txt"
|
||||
[[module.mounts]]
|
||||
source = "f3.txt"
|
||||
target = "static/f3.txt"
|
||||
[[module.mounts]]
|
||||
source = "static"
|
||||
target = "static"
|
||||
-- static/files/f2.txt --
|
||||
f2
|
||||
-- myfiles/f1.txt --
|
||||
f1
|
||||
-- f3.txt --
|
||||
f3
|
||||
-- layouts/home.html --
|
||||
Home.
|
||||
|
||||
`
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFs(b.H.BaseFs.StaticFs(""), `
|
||||
. true
|
||||
f3.txt false
|
||||
files true
|
||||
files/f1.txt false
|
||||
files/f2.txt false
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMountIssue12141(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ["taxonomy", "term"]
|
||||
[module]
|
||||
[[module.mounts]]
|
||||
source = "myfiles"
|
||||
target = "static"
|
||||
[[module.mounts]]
|
||||
source = "myfiles/f1.txt"
|
||||
target = "static/f2.txt"
|
||||
-- myfiles/f1.txt --
|
||||
f1
|
||||
`
|
||||
b := hugolib.Test(t, files)
|
||||
fs := b.H.BaseFs.StaticFs("")
|
||||
|
||||
b.AssertFs(fs, `
|
||||
. true
|
||||
f1.txt false
|
||||
f2.txt false
|
||||
`)
|
||||
}
|
||||
|
||||
func checkFileCount(fs afero.Fs, dirname string, c *qt.C, expected int) {
|
||||
c.Helper()
|
||||
count, _, err := countFilesAndGetFilenames(fs, dirname)
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(count, qt.Equals, expected)
|
||||
count, names, err := countFilesAndGetFilenames(fs, dirname)
|
||||
namesComment := qt.Commentf("filenames: %v", names)
|
||||
c.Assert(err, qt.IsNil, namesComment)
|
||||
c.Assert(count, qt.Equals, expected, namesComment)
|
||||
}
|
||||
|
||||
func checkFileContent(fs afero.Fs, filename string, c *qt.C, expected ...string) {
|
||||
|
||||
@@ -269,7 +269,7 @@ func (h *HugoSites) pickOneAndLogTheRest(errors []error) error {
|
||||
return errors[i]
|
||||
}
|
||||
|
||||
func (h *HugoSites) isMultiLingual() bool {
|
||||
func (h *HugoSites) isMultilingual() bool {
|
||||
return len(h.Sites) > 1
|
||||
}
|
||||
|
||||
@@ -410,6 +410,10 @@ type BuildCfg struct {
|
||||
|
||||
// shouldRender returns whether this output format should be rendered or not.
|
||||
func (cfg *BuildCfg) shouldRender(p *pageState) bool {
|
||||
if p.skipRender() {
|
||||
return false
|
||||
}
|
||||
|
||||
if !p.renderOnce {
|
||||
return true
|
||||
}
|
||||
|
||||
+52
-42
@@ -28,16 +28,16 @@ import (
|
||||
"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/segments"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/output"
|
||||
"github.com/gohugoio/hugo/publisher"
|
||||
"github.com/gohugoio/hugo/source"
|
||||
"github.com/gohugoio/hugo/tpl"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/para"
|
||||
@@ -236,7 +236,7 @@ func (h *HugoSites) process(ctx context.Context, l logg.LevelLogger, config *Bui
|
||||
return h.processFull(ctx, l, *config)
|
||||
}
|
||||
|
||||
// assemble creates missing sections, applies agregate values (e.g. dates, cascading params),
|
||||
// assemble creates missing sections, applies aggregate values (e.g. dates, cascading params),
|
||||
// removes disabled pages etc.
|
||||
func (h *HugoSites) assemble(ctx context.Context, l logg.LevelLogger, bcfg *BuildCfg) error {
|
||||
l = l.WithField("step", "assemble")
|
||||
@@ -282,11 +282,6 @@ func (h *HugoSites) assemble(ctx context.Context, l logg.LevelLogger, bcfg *Buil
|
||||
return err
|
||||
}
|
||||
}
|
||||
h.renderFormats = output.Formats{}
|
||||
for _, s := range h.Sites {
|
||||
s.s.initRenderFormats()
|
||||
h.renderFormats = append(h.renderFormats, s.renderFormats...)
|
||||
}
|
||||
|
||||
for _, s := range assemblers {
|
||||
if err := s.assemblePagesStep2(); err != nil {
|
||||
@@ -296,9 +291,16 @@ func (h *HugoSites) assemble(ctx context.Context, l logg.LevelLogger, bcfg *Buil
|
||||
|
||||
h.renderFormats = output.Formats{}
|
||||
for _, s := range h.Sites {
|
||||
s.s.initRenderFormats()
|
||||
h.renderFormats = append(h.renderFormats, s.renderFormats...)
|
||||
}
|
||||
|
||||
for _, s := range assemblers {
|
||||
if err := s.assemblePagesStepFinal(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -318,9 +320,20 @@ func (h *HugoSites) render(l logg.LevelLogger, config *BuildCfg) error {
|
||||
|
||||
i := 0
|
||||
for _, s := range h.Sites {
|
||||
segmentFilter := s.conf.C.SegmentFilter
|
||||
if segmentFilter.ShouldExcludeCoarse(segments.SegmentMatcherFields{Lang: s.language.Lang}) {
|
||||
l.Logf("skip language %q not matching segments set in --renderSegments", s.language.Lang)
|
||||
continue
|
||||
}
|
||||
|
||||
siteRenderContext.languageIdx = s.languagei
|
||||
h.currentSite = s
|
||||
for siteOutIdx, renderFormat := range s.renderFormats {
|
||||
if segmentFilter.ShouldExcludeCoarse(segments.SegmentMatcherFields{Output: renderFormat.Name, Lang: s.language.Lang}) {
|
||||
l.Logf("skip output format %q for language %q not matching segments set in --renderSegments", renderFormat.Name, s.language.Lang)
|
||||
continue
|
||||
}
|
||||
|
||||
siteRenderContext.outIdx = siteOutIdx
|
||||
siteRenderContext.sitesOutIdx = i
|
||||
i++
|
||||
@@ -595,8 +608,10 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
|
||||
return sb.String()
|
||||
}))
|
||||
|
||||
// For a list of events for the different OSes, see the test output in https://github.com/bep/fsnotifyeventlister/.
|
||||
events = h.fileEventsFilter(events)
|
||||
events = h.fileEventsTranslate(events)
|
||||
eventInfos := h.fileEventsApplyInfo(events)
|
||||
|
||||
logger := h.Log
|
||||
|
||||
@@ -631,36 +646,12 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
|
||||
addedContentPaths []*paths.Path
|
||||
)
|
||||
|
||||
for _, ev := range events {
|
||||
removed := false
|
||||
added := false
|
||||
|
||||
if ev.Op&fsnotify.Remove == fsnotify.Remove {
|
||||
removed = true
|
||||
}
|
||||
|
||||
fi, statErr := h.Fs.Source.Stat(ev.Name)
|
||||
|
||||
// Some editors (Vim) sometimes issue only a Rename operation when writing an existing file
|
||||
// Sometimes a rename operation means that file has been renamed other times it means
|
||||
// it's been updated.
|
||||
if ev.Op.Has(fsnotify.Rename) {
|
||||
// If the file is still on disk, it's only been updated, if it's not, it's been moved
|
||||
if statErr != nil {
|
||||
removed = true
|
||||
}
|
||||
}
|
||||
if ev.Op.Has(fsnotify.Create) {
|
||||
added = true
|
||||
}
|
||||
|
||||
isChangedDir := statErr == nil && fi.IsDir()
|
||||
|
||||
cpss := h.BaseFs.ResolvePaths(ev.Name, !removed)
|
||||
for _, ev := range eventInfos {
|
||||
cpss := h.BaseFs.ResolvePaths(ev.Name)
|
||||
pss := make([]*paths.Path, len(cpss))
|
||||
for i, cps := range cpss {
|
||||
p := cps.Path
|
||||
if removed && !paths.HasExt(p) {
|
||||
if ev.removed && !paths.HasExt(p) {
|
||||
// Assume this is a renamed/removed directory.
|
||||
// For deletes, we walk up the tree to find the container (e.g. branch bundle),
|
||||
// so we will catch this even if it is a file without extension.
|
||||
@@ -671,7 +662,7 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
|
||||
}
|
||||
|
||||
pss[i] = h.Configs.ContentPathParser.Parse(cps.Component, p)
|
||||
if added && !isChangedDir && cps.Component == files.ComponentFolderContent {
|
||||
if ev.added && !ev.isChangedDir && cps.Component == files.ComponentFolderContent {
|
||||
addedContentPaths = append(addedContentPaths, pss[i])
|
||||
}
|
||||
|
||||
@@ -683,9 +674,9 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
|
||||
}
|
||||
}
|
||||
|
||||
if removed {
|
||||
if ev.removed {
|
||||
changedPaths.deleted = append(changedPaths.deleted, pss...)
|
||||
} else if isChangedDir {
|
||||
} else if ev.isChangedDir {
|
||||
changedPaths.changedDirs = append(changedPaths.changedDirs, pss...)
|
||||
} else {
|
||||
changedPaths.changedFiles = append(changedPaths.changedFiles, pss...)
|
||||
@@ -702,7 +693,7 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
|
||||
switch pathInfo.Component() {
|
||||
case files.ComponentFolderContent:
|
||||
logger.Println("Source changed", pathInfo.Path())
|
||||
if ids := h.pageTrees.collectIdentities(pathInfo); len(ids) > 0 {
|
||||
if ids := h.pageTrees.collectAndMarkStaleIdentities(pathInfo); len(ids) > 0 {
|
||||
changes = append(changes, ids...)
|
||||
}
|
||||
|
||||
@@ -739,8 +730,7 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
|
||||
|
||||
case files.ComponentFolderLayouts:
|
||||
tmplChanged = true
|
||||
h.init.layouts.Reset()
|
||||
templatePath := pathInfo.TrimLeadingSlash().PathNoLang()
|
||||
templatePath := pathInfo.Unnormalized().TrimLeadingSlash().PathNoLang()
|
||||
if !h.Tmpl().HasTemplate(templatePath) {
|
||||
tmplAdded = true
|
||||
}
|
||||
@@ -790,7 +780,11 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
|
||||
case files.ComponentFolderI18n:
|
||||
logger.Println("i18n changed", pathInfo.Path())
|
||||
i18nChanged = true
|
||||
changes = append(changes, pathInfo)
|
||||
// It's hard to determine the exact change set of this,
|
||||
// so be very coarse grained for now.
|
||||
changes = append(changes, identity.GenghisKhan)
|
||||
case files.ComponentFolderArchetypes:
|
||||
// Ignore for now.
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown component: %q", pathInfo.Component()))
|
||||
}
|
||||
@@ -897,6 +891,9 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -922,9 +919,22 @@ func (h *HugoSites) processPartial(ctx context.Context, l logg.LevelLogger, conf
|
||||
}
|
||||
}
|
||||
|
||||
h.logServerAddresses()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
} else {
|
||||
s := h.Sites[0]
|
||||
h.Log.Printf("Web Server is available at %s (bind address %s)\n", s.conf.C.BaseURL, s.conf.C.ServerInterface)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -66,12 +66,12 @@ robots|{{ site.Language.Lang }}
|
||||
404|{{ site.Language.Lang }}
|
||||
|
||||
|
||||
|
||||
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.Assert(b.H.Conf.IsMultiLingual(), qt.Equals, true)
|
||||
b.Assert(b.H.Conf.IsMultilingual(), qt.Equals, true)
|
||||
b.Assert(b.H.Conf.IsMultihost(), qt.Equals, true)
|
||||
|
||||
// helpers.PrintFs(b.H.Fs.PublishDir, "", os.Stdout)
|
||||
@@ -162,6 +162,52 @@ title: "Mybundle fr"
|
||||
b.AssertFileContent("public/fr/section/mybundle/styles.min.css", ".body{color:french}")
|
||||
}
|
||||
|
||||
func TestResourcePerLanguageIssue12163(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
defaultContentLanguage = 'de'
|
||||
disableKinds = ['rss','sitemap','taxonomy','term']
|
||||
|
||||
[languages.de]
|
||||
baseURL = 'https://de.example.org/'
|
||||
contentDir = 'content/de'
|
||||
weight = 1
|
||||
|
||||
[languages.en]
|
||||
baseURL = 'https://en.example.org/'
|
||||
contentDir = 'content/en'
|
||||
weight = 2
|
||||
-- content/de/mybundle/index.md --
|
||||
---
|
||||
title: mybundle-de
|
||||
---
|
||||
-- content/de/mybundle/pixel.png --
|
||||
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
|
||||
-- content/en/mybundle/index.md --
|
||||
---
|
||||
title: mybundle-en
|
||||
---
|
||||
-- layouts/_default/single.html --
|
||||
{{ with .Resources.Get "pixel.png" }}
|
||||
{{ with .Resize "2x2" }}
|
||||
{{ .RelPermalink }}|
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileExists("public/de/mybundle/index.html", true)
|
||||
b.AssertFileExists("public/en/mybundle/index.html", true)
|
||||
|
||||
b.AssertFileExists("public/de/mybundle/pixel.png", true)
|
||||
b.AssertFileExists("public/en/mybundle/pixel.png", true)
|
||||
|
||||
b.AssertFileExists("public/de/mybundle/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_2x2_resize_box_3.png", true)
|
||||
// failing test below
|
||||
b.AssertFileExists("public/en/mybundle/pixel_hu8aa3346827e49d756ff4e630147c42b5_70_2x2_resize_box_3.png", true)
|
||||
}
|
||||
|
||||
func TestMultihostResourceOneBaseURLWithSuPath(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
|
||||
@@ -169,10 +169,11 @@ type IntegrationTestBuilder struct {
|
||||
renamedFiles []string
|
||||
renamedDirs []string
|
||||
|
||||
buildCount int
|
||||
GCCount int
|
||||
counters *buildCounters
|
||||
logBuff lockingBuffer
|
||||
buildCount int
|
||||
GCCount int
|
||||
counters *buildCounters
|
||||
logBuff lockingBuffer
|
||||
lastBuildLog string
|
||||
|
||||
builderInit sync.Once
|
||||
}
|
||||
@@ -182,6 +183,13 @@ type lockingBuffer struct {
|
||||
bytes.Buffer
|
||||
}
|
||||
|
||||
func (b *lockingBuffer) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
b.Lock()
|
||||
n, err = b.Buffer.ReadFrom(r)
|
||||
b.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (b *lockingBuffer) Write(p []byte) (n int, err error) {
|
||||
b.Lock()
|
||||
n, err = b.Buffer.Write(p)
|
||||
@@ -192,21 +200,21 @@ func (b *lockingBuffer) Write(p []byte) (n int, err error) {
|
||||
func (s *IntegrationTestBuilder) AssertLogContains(els ...string) {
|
||||
s.Helper()
|
||||
for _, el := range els {
|
||||
s.Assert(s.logBuff.String(), qt.Contains, el)
|
||||
s.Assert(s.lastBuildLog, qt.Contains, el)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) AssertLogNotContains(els ...string) {
|
||||
s.Helper()
|
||||
for _, el := range els {
|
||||
s.Assert(s.logBuff.String(), qt.Not(qt.Contains), el)
|
||||
s.Assert(s.lastBuildLog, qt.Not(qt.Contains), el)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) AssertLogMatches(expression string) {
|
||||
s.Helper()
|
||||
re := regexp.MustCompile(expression)
|
||||
s.Assert(re.MatchString(s.logBuff.String()), qt.IsTrue, qt.Commentf(s.logBuff.String()))
|
||||
s.Assert(re.MatchString(s.lastBuildLog), qt.IsTrue, qt.Commentf(s.lastBuildLog))
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) AssertBuildCountData(count int) {
|
||||
@@ -275,9 +283,13 @@ func (s *IntegrationTestBuilder) AssertFileContentExact(filename string, matches
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) AssertPublishDir(matches ...string) {
|
||||
s.AssertFs(s.fs.PublishDir, matches...)
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) AssertFs(fs afero.Fs, matches ...string) {
|
||||
s.Helper()
|
||||
var buff bytes.Buffer
|
||||
helpers.PrintFs(s.H.Fs.PublishDir, "", &buff)
|
||||
s.Assert(s.printAndCheckFs(fs, "", &buff), qt.IsNil)
|
||||
printFsLines := strings.Split(buff.String(), "\n")
|
||||
sort.Strings(printFsLines)
|
||||
content := strings.TrimSpace((strings.Join(printFsLines, "\n")))
|
||||
@@ -300,6 +312,34 @@ func (s *IntegrationTestBuilder) AssertPublishDir(matches ...string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) printAndCheckFs(fs afero.Fs, path string, w io.Writer) error {
|
||||
if fs == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return afero.Walk(fs, path, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("error: path %q: %s", path, err)
|
||||
}
|
||||
path = filepath.ToSlash(path)
|
||||
if path == "" {
|
||||
path = "."
|
||||
}
|
||||
if !info.IsDir() {
|
||||
f, err := fs.Open(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error: path %q: %s", path, err)
|
||||
}
|
||||
defer f.Close()
|
||||
// This will panic if the file is a directory.
|
||||
var buf [1]byte
|
||||
io.ReadFull(f, buf[:])
|
||||
}
|
||||
fmt.Fprintln(w, path, info.IsDir())
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) AssertFileExists(filename string, b bool) {
|
||||
checker := qt.IsNil
|
||||
if !b {
|
||||
@@ -337,7 +377,7 @@ func (s *IntegrationTestBuilder) Build() *IntegrationTestBuilder {
|
||||
s.Helper()
|
||||
_, err := s.BuildE()
|
||||
if s.Cfg.Verbose || err != nil {
|
||||
fmt.Println(s.logBuff.String())
|
||||
fmt.Println(s.lastBuildLog)
|
||||
if s.H != nil && err == nil {
|
||||
for _, s := range s.H.Sites {
|
||||
m := s.pageMap
|
||||
@@ -348,7 +388,7 @@ func (s *IntegrationTestBuilder) Build() *IntegrationTestBuilder {
|
||||
}
|
||||
}
|
||||
} else if s.Cfg.LogLevel <= logg.LevelDebug {
|
||||
fmt.Println(s.logBuff.String())
|
||||
fmt.Println(s.lastBuildLog)
|
||||
}
|
||||
s.Assert(err, qt.IsNil)
|
||||
if s.Cfg.RunGC {
|
||||
@@ -360,7 +400,7 @@ func (s *IntegrationTestBuilder) Build() *IntegrationTestBuilder {
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) LogString() string {
|
||||
return s.logBuff.String()
|
||||
return s.lastBuildLog
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) BuildE() (*IntegrationTestBuilder, error) {
|
||||
@@ -377,6 +417,7 @@ func (s *IntegrationTestBuilder) Init() *IntegrationTestBuilder {
|
||||
if err := s.initBuilder(); err != nil {
|
||||
s.Fatalf("Failed to init builder: %s", err)
|
||||
}
|
||||
s.lastBuildLog = s.logBuff.String()
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -622,10 +663,11 @@ func (s *IntegrationTestBuilder) build(cfg BuildCfg) error {
|
||||
s.Helper()
|
||||
defer func() {
|
||||
s.reset()
|
||||
s.lastBuildLog = s.logBuff.String()
|
||||
s.logBuff.Reset()
|
||||
}()
|
||||
|
||||
changeEvents := s.changeEvents()
|
||||
s.logBuff.Reset()
|
||||
s.counters = &buildCounters{}
|
||||
cfg.testCounters = s.counters
|
||||
|
||||
@@ -639,10 +681,6 @@ func (s *IntegrationTestBuilder) build(cfg BuildCfg) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logErrorCount := s.H.NumLogErrors()
|
||||
if logErrorCount > 0 {
|
||||
return fmt.Errorf("logged %d error(s): %s", logErrorCount, s.logBuff.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -636,3 +636,43 @@ Menu Item: {{ $i }}|{{ .URL }}|
|
||||
Menu Item: 0|/foo/posts|
|
||||
`)
|
||||
}
|
||||
|
||||
func TestSectionPagesMenuMultilingualWarningIssue12306(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['section','rss','sitemap','taxonomy','term']
|
||||
defaultContentLanguageInSubdir = true
|
||||
sectionPagesMenu = "main"
|
||||
[languages.en]
|
||||
[languages.fr]
|
||||
-- layouts/_default/home.html --
|
||||
{{- range site.Menus.main -}}
|
||||
<a href="{{ .URL }}">{{ .Name }}</a>
|
||||
{{- end -}}
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Title }}
|
||||
-- content/p1.en.md --
|
||||
---
|
||||
title: p1
|
||||
menu: main
|
||||
---
|
||||
-- content/p1.fr.md --
|
||||
---
|
||||
title: p1
|
||||
menu: main
|
||||
---
|
||||
-- content/p2.en.md --
|
||||
---
|
||||
title: p2
|
||||
menu: main
|
||||
---
|
||||
`
|
||||
|
||||
b := Test(t, files, TestOptWarn())
|
||||
|
||||
b.AssertFileContent("public/en/index.html", `<a href="/en/p1/">p1</a><a href="/en/p2/">p2</a>`)
|
||||
b.AssertFileContent("public/fr/index.html", `<a href="/fr/p1/">p1</a>`)
|
||||
b.AssertLogNotContains("WARN")
|
||||
}
|
||||
|
||||
+27
-4
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/hugolib/doctree"
|
||||
"github.com/gohugoio/hugo/hugolib/segments"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/gohugoio/hugo/output"
|
||||
@@ -36,6 +37,7 @@ import (
|
||||
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
|
||||
"github.com/gohugoio/hugo/source"
|
||||
|
||||
@@ -120,8 +122,8 @@ func (p *pageState) GetIdentity() identity.Identity {
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *pageState) ForEeachIdentity(f func(identity.Identity) bool) {
|
||||
f(p)
|
||||
func (p *pageState) ForEeachIdentity(f func(identity.Identity) bool) bool {
|
||||
return f(p)
|
||||
}
|
||||
|
||||
func (p *pageState) GetDependencyManager() identity.Manager {
|
||||
@@ -151,6 +153,19 @@ func (p *pageState) reusePageOutputContent() bool {
|
||||
return p.pageOutputTemplateVariationsState.Load() == 1
|
||||
}
|
||||
|
||||
func (p *pageState) skipRender() bool {
|
||||
b := p.s.conf.C.SegmentFilter.ShouldExcludeFine(
|
||||
segments.SegmentMatcherFields{
|
||||
Path: p.Path(),
|
||||
Kind: p.Kind(),
|
||||
Lang: p.Lang(),
|
||||
Output: p.pageOutput.f.Name,
|
||||
},
|
||||
)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (po *pageState) isRenderedAny() bool {
|
||||
for _, o := range po.pageOutputs {
|
||||
if o.isRendered() {
|
||||
@@ -365,7 +380,9 @@ func (p *pageState) TranslationKey() string {
|
||||
// AllTranslations returns all translations, including the current Page.
|
||||
func (p *pageState) AllTranslations() page.Pages {
|
||||
key := p.Path() + "/" + "translations-all"
|
||||
pages, err := p.s.pageMap.getOrCreatePagesFromCache(key, func(string) (page.Pages, error) {
|
||||
// This is called from Translations, so we need to use a different partition, cachePages2,
|
||||
// to avoid potential deadlocks.
|
||||
pages, err := p.s.pageMap.getOrCreatePagesFromCache(p.s.pageMap.cachePages2, key, func(string) (page.Pages, error) {
|
||||
if p.m.pageConfig.TranslationKey != "" {
|
||||
// translationKey set by user.
|
||||
pas, _ := p.s.h.translationKeyPages.Get(p.m.pageConfig.TranslationKey)
|
||||
@@ -398,7 +415,7 @@ func (p *pageState) AllTranslations() page.Pages {
|
||||
// Translations returns the translations excluding the current Page.
|
||||
func (p *pageState) Translations() page.Pages {
|
||||
key := p.Path() + "/" + "translations"
|
||||
pages, err := p.s.pageMap.getOrCreatePagesFromCache(key, func(string) (page.Pages, error) {
|
||||
pages, err := p.s.pageMap.getOrCreatePagesFromCache(nil, key, func(string) (page.Pages, error) {
|
||||
var pas page.Pages
|
||||
for _, pp := range p.AllTranslations() {
|
||||
if !pp.Eq(p) {
|
||||
@@ -731,3 +748,9 @@ func (p pageWithWeight0) Weight0() int {
|
||||
func (p pageWithWeight0) page() page.Page {
|
||||
return p.pageState
|
||||
}
|
||||
|
||||
var _ types.Unwrapper = (*pageWithWeight0)(nil)
|
||||
|
||||
func (p pageWithWeight0) Unwrapv() any {
|
||||
return p.pageState
|
||||
}
|
||||
|
||||
@@ -770,7 +770,7 @@ func (c *cachedContent) contentPlain(ctx context.Context, cp *pageContentOutput)
|
||||
result.readingTime = (result.wordCount + 212) / 213
|
||||
}
|
||||
|
||||
if rendered.summary != "" {
|
||||
if c.pi.hasSummaryDivider || rendered.summary != "" {
|
||||
result.summary = rendered.summary
|
||||
result.summaryTruncated = rendered.summaryTruncated
|
||||
} else if cp.po.p.m.pageConfig.Summary != "" {
|
||||
|
||||
@@ -44,7 +44,7 @@ func (p *pageData) Data() any {
|
||||
p.data[name.singular] = term
|
||||
p.data["Singular"] = name.singular
|
||||
p.data["Plural"] = name.plural
|
||||
p.data["Term"] = p.Title()
|
||||
p.data["Term"] = p.m.term
|
||||
case kinds.KindTaxonomy:
|
||||
viewCfg := p.s.pageMap.cfg.getTaxonomyConfig(p.Path())
|
||||
p.data["Singular"] = viewCfg.singular
|
||||
|
||||
+23
-12
@@ -92,7 +92,7 @@ type pageMetaParams struct {
|
||||
|
||||
// From page front matter.
|
||||
type pageMetaFrontMatter struct {
|
||||
configuredOutputFormats output.Formats // outputs defiend in front matter.
|
||||
configuredOutputFormats output.Formats // outputs defined in front matter.
|
||||
}
|
||||
|
||||
func (m *pageMetaParams) init(preserveOringal bool) {
|
||||
@@ -106,9 +106,9 @@ func (p *pageMeta) Aliases() []string {
|
||||
return p.pageConfig.Aliases
|
||||
}
|
||||
|
||||
// Deprecated: use taxonomies.
|
||||
// Deprecated: Use taxonomies instead.
|
||||
func (p *pageMeta) Author() page.Author {
|
||||
hugo.Deprecate(".Author", "Use taxonomies.", "v0.98.0")
|
||||
hugo.Deprecate(".Page.Author", "Use taxonomies instead.", "v0.98.0")
|
||||
authors := p.Authors()
|
||||
|
||||
for _, author := range authors {
|
||||
@@ -117,9 +117,9 @@ func (p *pageMeta) Author() page.Author {
|
||||
return page.Author{}
|
||||
}
|
||||
|
||||
// Deprecated: use taxonomies.
|
||||
// Deprecated: Use taxonomies instead.
|
||||
func (p *pageMeta) Authors() page.AuthorList {
|
||||
hugo.Deprecate(".Author", "Use taxonomies.", "v0.112.0")
|
||||
hugo.Deprecate(".Page.Authors", "Use taxonomies instead.", "v0.112.0")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@ func (ps *pageState) setMetaPost(cascade map[page.PageMatcher]maps.Params) error
|
||||
|
||||
}
|
||||
|
||||
// Apply cascades first so they can be overriden later.
|
||||
// Apply cascades first so they can be overridden later.
|
||||
if cascade != nil {
|
||||
if ps.m.pageConfig.Cascade != nil {
|
||||
for k, v := range cascade {
|
||||
@@ -446,7 +446,7 @@ title: "My Title"
|
||||
params:
|
||||
build: "My Build"
|
||||
---
|
||||
´
|
||||
´
|
||||
|
||||
`
|
||||
}
|
||||
@@ -523,7 +523,7 @@ params:
|
||||
// pages.
|
||||
isHeadless := cast.ToBool(v)
|
||||
params[loki] = isHeadless
|
||||
if p.File().TranslationBaseName() == "index" && isHeadless {
|
||||
if isHeadless {
|
||||
pm.pageConfig.Build.List = pagemeta.Never
|
||||
pm.pageConfig.Build.Render = pagemeta.Never
|
||||
}
|
||||
@@ -676,7 +676,7 @@ params:
|
||||
}
|
||||
|
||||
// shouldList returns whether this page should be included in the list of pages.
|
||||
// glogal indicates site.Pages etc.
|
||||
// global indicates site.Pages etc.
|
||||
func (p *pageMeta) shouldList(global bool) bool {
|
||||
if p.isStandalone() {
|
||||
// Never list 404, sitemap and similar.
|
||||
@@ -746,15 +746,26 @@ func (p *pageMeta) applyDefaultValues() error {
|
||||
if p.s.conf.PluralizeListTitles {
|
||||
sectionName = flect.Pluralize(sectionName)
|
||||
}
|
||||
p.pageConfig.Title = p.s.conf.C.CreateTitle(sectionName)
|
||||
if p.s.conf.CapitalizeListTitles {
|
||||
sectionName = p.s.conf.C.CreateTitle(sectionName)
|
||||
}
|
||||
p.pageConfig.Title = sectionName
|
||||
case kinds.KindTerm:
|
||||
if p.term != "" {
|
||||
p.pageConfig.Title = p.s.conf.C.CreateTitle(p.term)
|
||||
if p.s.conf.CapitalizeListTitles {
|
||||
p.pageConfig.Title = p.s.conf.C.CreateTitle(p.term)
|
||||
} else {
|
||||
p.pageConfig.Title = p.term
|
||||
}
|
||||
} else {
|
||||
panic("term not set")
|
||||
}
|
||||
case kinds.KindTaxonomy:
|
||||
p.pageConfig.Title = strings.Replace(p.s.conf.C.CreateTitle(p.pathInfo.Unnormalized().BaseNameNoIdentifier()), "-", " ", -1)
|
||||
if p.s.conf.CapitalizeListTitles {
|
||||
p.pageConfig.Title = strings.Replace(p.s.conf.C.CreateTitle(p.pathInfo.Unnormalized().BaseNameNoIdentifier()), "-", " ", -1)
|
||||
} else {
|
||||
p.pageConfig.Title = strings.Replace(p.pathInfo.Unnormalized().BaseNameNoIdentifier(), "-", " ", -1)
|
||||
}
|
||||
case kinds.KindStatus404:
|
||||
p.pageConfig.Title = "404 Page not found"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// 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 hugolib
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Issue 9793
|
||||
// Issue 12115
|
||||
func TestListTitles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','rss','sitemap']
|
||||
capitalizeListTitles = true
|
||||
pluralizeListTitles = true
|
||||
[taxonomies]
|
||||
tag = 'tags'
|
||||
-- content/section-1/page-1.md --
|
||||
---
|
||||
title: page-1
|
||||
tags: 'tag-a'
|
||||
---
|
||||
-- layouts/_default/list.html --
|
||||
{{ .Title }}
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Title }}
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/section-1/index.html", "Section-1s")
|
||||
b.AssertFileContent("public/tags/index.html", "Tags")
|
||||
b.AssertFileContent("public/tags/tag-a/index.html", "Tag-A")
|
||||
|
||||
files = strings.Replace(files, "true", "false", -1)
|
||||
|
||||
b = Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/section-1/index.html", "section-1")
|
||||
b.AssertFileContent("public/tags/index.html", "tags")
|
||||
b.AssertFileContent("public/tags/tag-a/index.html", "tag-a")
|
||||
}
|
||||
|
||||
func TestDraftNonDefaultContentLanguage(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
defaultContentLanguage = "en"
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
[languages.nn]
|
||||
weight = 2
|
||||
-- content/p1.md --
|
||||
-- content/p2.nn.md --
|
||||
---
|
||||
title: "p2"
|
||||
draft: true
|
||||
---
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Title }}
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileExists("public/nn/p2/index.html", false)
|
||||
}
|
||||
@@ -20,7 +20,6 @@ import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/resources"
|
||||
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
@@ -146,9 +145,11 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) {
|
||||
// Either a taxonomy or a term.
|
||||
if tc.pluralTreeKey == m.Path() {
|
||||
m.pageConfig.Kind = kinds.KindTaxonomy
|
||||
m.singular = tc.singular
|
||||
} else {
|
||||
m.pageConfig.Kind = kinds.KindTerm
|
||||
m.term = m.pathInfo.Unnormalized().BaseNameNoIdentifier()
|
||||
m.singular = tc.singular
|
||||
}
|
||||
}
|
||||
} else if m.f != nil {
|
||||
@@ -160,12 +161,6 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var dependencyManager identity.Manager = identity.NopManager
|
||||
|
||||
if m.s.conf.Internal.Watch {
|
||||
dependencyManager = identity.NewManager(m.Path())
|
||||
}
|
||||
|
||||
// Parse the rest of the page content.
|
||||
m.content, err = m.newCachedContent(h, pi)
|
||||
if err != nil {
|
||||
@@ -178,7 +173,7 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) {
|
||||
pageOutputTemplateVariationsState: &atomic.Uint32{},
|
||||
resourcesPublishInit: &sync.Once{},
|
||||
Staler: m,
|
||||
dependencyManager: dependencyManager,
|
||||
dependencyManager: m.s.Conf.NewIdentityManager(m.Path()),
|
||||
pageCommon: &pageCommon{
|
||||
FileProvider: m,
|
||||
AuthorProvider: m,
|
||||
|
||||
@@ -51,11 +51,6 @@ func newPageOutput(
|
||||
})
|
||||
}
|
||||
|
||||
var dependencyManager identity.Manager = identity.NopManager
|
||||
if ps.s.conf.Internal.Watch {
|
||||
dependencyManager = identity.NewManager(ps.Path() + "/" + f.Name)
|
||||
}
|
||||
|
||||
providers := struct {
|
||||
page.PaginatorProvider
|
||||
resource.ResourceLinksProvider
|
||||
@@ -75,7 +70,7 @@ func newPageOutput(
|
||||
TableOfContentsProvider: page.NopPage,
|
||||
render: render,
|
||||
paginator: pag,
|
||||
dependencyManagerOutput: dependencyManager,
|
||||
dependencyManagerOutput: ps.s.Conf.NewIdentityManager((ps.Path() + "/" + f.Name)),
|
||||
}
|
||||
|
||||
return po
|
||||
|
||||
@@ -150,7 +150,7 @@ func createTargetPathDescriptor(p *pageState) (page.TargetPathDescriptor, error)
|
||||
opath, _ = url.QueryUnescape(opath)
|
||||
if strings.HasSuffix(opath, "//") {
|
||||
// When rewriting the _index of the section the permalink config is applied to,
|
||||
// we get douple slashes at the end sometimes; clear them up here
|
||||
// we get double slashes at the end sometimes; clear them up here
|
||||
opath = strings.TrimSuffix(opath, "/")
|
||||
}
|
||||
|
||||
|
||||
@@ -124,11 +124,16 @@ func (pt pageTree) Parent() page.Page {
|
||||
return pt.p.s.home
|
||||
}
|
||||
|
||||
_, n := pt.p.s.pageMap.treePages.LongestPrefix(dir, true, nil)
|
||||
if n != nil {
|
||||
return n.(page.Page)
|
||||
for {
|
||||
_, n := pt.p.s.pageMap.treePages.LongestPrefix(dir, true, nil)
|
||||
if n == nil {
|
||||
return pt.p.s.home
|
||||
}
|
||||
if pt.p.m.bundled || n.isContentNodeBranch() {
|
||||
return n.(page.Page)
|
||||
}
|
||||
dir = paths.Dir(dir)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pt pageTree) Ancestors() page.Pages {
|
||||
|
||||
+73
-3
@@ -63,6 +63,15 @@ Summary Next Line
|
||||
|
||||
<!--more-->
|
||||
Some more text
|
||||
`
|
||||
|
||||
simplePageWithBlankSummary = `---
|
||||
title: SimpleWithBlankSummary
|
||||
---
|
||||
|
||||
<!--more-->
|
||||
|
||||
Some text.
|
||||
`
|
||||
|
||||
simplePageWithSummaryParameter = `---
|
||||
@@ -351,6 +360,9 @@ func normalizeExpected(ext, str string) string {
|
||||
|
||||
return expected
|
||||
case "rst":
|
||||
if str == "" {
|
||||
return "<div class=\"document\"></div>"
|
||||
}
|
||||
return fmt.Sprintf("<div class=\"document\">\n\n\n%s</div>", str)
|
||||
}
|
||||
}
|
||||
@@ -578,7 +590,7 @@ date: 2012-01-12
|
||||
b.Assert(s.getPageOldVersion("/with-index-no-date").Date().IsZero(), qt.Equals, true)
|
||||
checkDate(s.getPageOldVersion("/with-index-date"), 2018)
|
||||
|
||||
b.Assert(s.Site().LastChange().Year(), qt.Equals, 2018)
|
||||
b.Assert(s.Site().Lastmod().Year(), qt.Equals, 2018)
|
||||
}
|
||||
|
||||
func TestCreateNewPage(t *testing.T) {
|
||||
@@ -630,6 +642,19 @@ func TestPageWithDelimiter(t *testing.T) {
|
||||
testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiter)
|
||||
}
|
||||
|
||||
func TestPageWithBlankSummary(t *testing.T) {
|
||||
t.Parallel()
|
||||
assertFunc := func(t *testing.T, ext string, pages page.Pages) {
|
||||
p := pages[0]
|
||||
checkPageTitle(t, p, "SimpleWithBlankSummary")
|
||||
checkPageContent(t, p, normalizeExpected(ext, "<p>Some text.</p>\n"), ext)
|
||||
checkPageSummary(t, p, normalizeExpected(ext, ""), ext)
|
||||
checkPageType(t, p, "page")
|
||||
}
|
||||
|
||||
testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithBlankSummary)
|
||||
}
|
||||
|
||||
func TestPageWithSummaryParameter(t *testing.T) {
|
||||
t.Parallel()
|
||||
assertFunc := func(t *testing.T, ext string, pages page.Pages) {
|
||||
@@ -709,7 +734,7 @@ func TestPageWithShortCodeInSummary(t *testing.T) {
|
||||
assertFunc := func(t *testing.T, ext string, pages page.Pages) {
|
||||
p := pages[0]
|
||||
checkPageTitle(t, p, "Simple")
|
||||
checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Next Line. <figure><img src=\"/not/real\"/> </figure> . More text here.</p><p>Some more text</p>"))
|
||||
checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Next Line. <figure><img src=\"/not/real\"> </figure> . More text here.</p><p>Some more text</p>"))
|
||||
checkPageSummary(t, p, "Summary Next Line. . More text here. Some more text")
|
||||
checkPageType(t, p, "page")
|
||||
}
|
||||
@@ -773,7 +798,7 @@ func TestSummaryManualSplit(t *testing.T) {
|
||||
title: Simple
|
||||
---
|
||||
This is **summary**.
|
||||
<!--more-->
|
||||
<!--more-->
|
||||
This is **content**.
|
||||
-- layouts/_default/single.html --
|
||||
Summary: {{ .Summary }}|Truncated: {{ .Truncated }}|
|
||||
@@ -1350,6 +1375,51 @@ AllTranslations: {{ range .AllTranslations }}{{ .Language.Lang }}|{{ end }}|
|
||||
)
|
||||
}
|
||||
|
||||
func TestTranslationKeyTermPages(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','rss','section','sitemap','taxonomy']
|
||||
defaultContentLanguage = 'en'
|
||||
defaultContentLanguageInSubdir = true
|
||||
[languages.en]
|
||||
weight = 1
|
||||
[languages.pt]
|
||||
weight = 2
|
||||
[taxonomies]
|
||||
category = 'categories'
|
||||
-- layouts/_default/list.html --
|
||||
{{ .IsTranslated }}|{{ range .Translations }}{{ .RelPermalink }}|{{ end }}
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Title }}|
|
||||
-- content/p1.en.md --
|
||||
---
|
||||
title: p1 (en)
|
||||
categories: [music]
|
||||
---
|
||||
-- content/p1.pt.md --
|
||||
---
|
||||
title: p1 (pt)
|
||||
categories: [música]
|
||||
---
|
||||
-- content/categories/music/_index.en.md --
|
||||
---
|
||||
title: music
|
||||
translationKey: foo
|
||||
---
|
||||
-- content/categories/música/_index.pt.md --
|
||||
---
|
||||
title: música
|
||||
translationKey: foo
|
||||
---
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
b.AssertFileContent("public/en/categories/music/index.html", "true|/pt/categories/m%C3%BAsica/|")
|
||||
b.AssertFileContent("public/pt/categories/música/index.html", "true|/en/categories/music/|")
|
||||
}
|
||||
|
||||
// Issue #11540.
|
||||
func TestTranslationKeyResourceSharing(t *testing.T) {
|
||||
files := `
|
||||
|
||||
@@ -516,7 +516,7 @@ baseURL = "https://example.org"
|
||||
disableKinds = ["taxonomy", "term"]
|
||||
disableLiveReload = true
|
||||
[outputs]
|
||||
# This looks odd, but it triggers the behaviour in #5858
|
||||
# This looks odd, but it triggers the behavior in #5858
|
||||
# The total output formats list gets sorted, so CSS before HTML.
|
||||
home = [ "CSS" ]
|
||||
-- content/mybundle/index.md --
|
||||
@@ -871,8 +871,90 @@ RegularPages: {{ range .RegularPages }}{{ .RelPermalink }}|File LogicalName: {{
|
||||
b := Test(t, files)
|
||||
|
||||
// Note that the sort order gives us the most specific data file for the en language (the data.en.json).
|
||||
b.AssertFileContent("public/mysection/mybundle/index.html", `Single:|/mysection/mybundle|File LogicalName: index.md||/mysection/mybundle/|page|Resources: data.json: Data JSON.|foo/p1.html: |p1.html: |p1.md: |data.txt: Data en txt.|$`)
|
||||
b.AssertFileContent("public/mysection/mybundle/index.html", `Single:|/mysection/mybundle|File LogicalName: index.md||/mysection/mybundle/|page|Resources: data.en.txt: Data en txt.|data.json: Data JSON.|foo/p1.html: |p1.html: |p1.md: |$`)
|
||||
b.AssertFileContent("public/mysection/index.html",
|
||||
"List: |/mysection|File LogicalName: _index.md|/mysection/|section|Resources: sectiondata.json: Secion data JSON.|sectiondata.txt: Section data TXT.|$",
|
||||
"RegularPages: /mysection/foo/p2/|File LogicalName: p2.md|/mysection/mybundle/|File LogicalName: index.md|/mysection/p2/|File LogicalName: p2.md|$")
|
||||
}
|
||||
|
||||
func TestBundleResourcesGetMatchOriginalName(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
-- content/mybundle/index.md --
|
||||
-- content/mybundle/f1.en.txt --
|
||||
F1.
|
||||
-- layouts/_default/single.html --
|
||||
GetMatch: {{ with .Resources.GetMatch "f1.en.*" }}{{ .Name }}: {{ .Content }}|{{ end }}
|
||||
Match: {{ range .Resources.Match "f1.En.*" }}{{ .Name }}: {{ .Content }}|{{ end }}
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/mybundle/index.html", "GetMatch: f1.en.txt: F1.|", "Match: f1.en.txt: F1.|")
|
||||
}
|
||||
|
||||
func TestBundleResourcesWhenLanguageVariantIsDraft(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
defaultContentLanguage = "en"
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
[languages.nn]
|
||||
weight = 2
|
||||
-- content/mybundle/index.en.md --
|
||||
-- content/mybundle/index.nn.md --
|
||||
---
|
||||
draft: true
|
||||
---
|
||||
-- content/mybundle/f1.en.txt --
|
||||
F1.
|
||||
-- layouts/_default/single.html --
|
||||
GetMatch: {{ with .Resources.GetMatch "f1.*" }}{{ .Name }}: {{ .Content }}|{{ end }}$
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/mybundle/index.html", "GetMatch: f1.en.txt: F1.|")
|
||||
}
|
||||
|
||||
func TestBundleBranchIssue12320(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['rss','sitemap','taxonomy','term']
|
||||
defaultContentLanguage = 'en'
|
||||
defaultContentLanguageInSubdir = true
|
||||
[languages.en]
|
||||
baseURL = "https://en.example.org/"
|
||||
contentDir = "content/en"
|
||||
[languages.fr]
|
||||
baseURL = "https://fr.example.org/"
|
||||
contentDir = "content/fr"
|
||||
-- content/en/s1/p1.md --
|
||||
---
|
||||
title: p1
|
||||
---
|
||||
-- content/en/s1/p1.txt --
|
||||
---
|
||||
p1.txt
|
||||
---
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Title }}|
|
||||
-- layouts/_default/list.html --
|
||||
{{ .Title }}|
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileExists("public/en/s1/index.html", true)
|
||||
b.AssertFileExists("public/en/s1/p1/index.html", true)
|
||||
b.AssertFileExists("public/en/s1/p1.txt", true)
|
||||
|
||||
b.AssertFileExists("public/fr/s1/index.html", false)
|
||||
b.AssertFileExists("public/fr/s1/p1/index.html", false)
|
||||
b.AssertFileExists("public/fr/s1/p1.txt", false) // failing test
|
||||
}
|
||||
|
||||
+20
-27
@@ -56,7 +56,7 @@ func (c *pageFinder) getPageRef(context page.Page, ref string) (page.Page, error
|
||||
}
|
||||
|
||||
func (c *pageFinder) getPage(context page.Page, ref string) (page.Page, error) {
|
||||
n, err := c.getContentNode(context, false, paths.ToSlashTrimTrailing(ref))
|
||||
n, err := c.getContentNode(context, false, ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -121,6 +121,7 @@ func (c *pageFinder) getPageForRefs(ref ...string) (page.Page, error) {
|
||||
const defaultContentExt = ".md"
|
||||
|
||||
func (c *pageFinder) getContentNode(context page.Page, isReflink bool, ref string) (contentNodeI, error) {
|
||||
ref = paths.ToSlashTrimTrailing(ref)
|
||||
inRef := ref
|
||||
if ref == "" {
|
||||
ref = "/"
|
||||
@@ -130,22 +131,21 @@ func (c *pageFinder) getContentNode(context page.Page, isReflink bool, ref strin
|
||||
return c.getContentNodeForRef(context, isReflink, true, inRef, ref)
|
||||
}
|
||||
|
||||
var refs []string
|
||||
|
||||
// We are always looking for a content file and having an extension greatly simplifies the code that follows,
|
||||
// even in the case where the extension does not match this one.
|
||||
if ref == "/" {
|
||||
refs = append(refs, "/_index"+defaultContentExt)
|
||||
if n, err := c.getContentNodeForRef(context, isReflink, false, inRef, "/_index"+defaultContentExt); n != nil || err != nil {
|
||||
return n, err
|
||||
}
|
||||
} else if strings.HasSuffix(ref, "/index") {
|
||||
refs = append(refs, ref+"/index"+defaultContentExt)
|
||||
refs = append(refs, ref+defaultContentExt)
|
||||
if n, err := c.getContentNodeForRef(context, isReflink, false, inRef, ref+"/index"+defaultContentExt); n != nil || err != nil {
|
||||
return n, err
|
||||
}
|
||||
if n, err := c.getContentNodeForRef(context, isReflink, false, inRef, ref+defaultContentExt); n != nil || err != nil {
|
||||
return n, err
|
||||
}
|
||||
} else {
|
||||
refs = append(refs, ref+defaultContentExt)
|
||||
}
|
||||
|
||||
for _, ref := range refs {
|
||||
n, err := c.getContentNodeForRef(context, isReflink, false, inRef, ref)
|
||||
if n != nil || err != nil {
|
||||
if n, err := c.getContentNodeForRef(context, isReflink, false, inRef, ref+defaultContentExt); n != nil || err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
@@ -165,21 +165,16 @@ func (c *pageFinder) getContentNodeForRef(context page.Page, isReflink, hadExten
|
||||
// Given the above, for regular pages we use the containing folder.
|
||||
var baseDir string
|
||||
if pi := context.PathInfo(); pi != nil {
|
||||
if pi.IsBranchBundle() || (hadExtension) {
|
||||
if pi.IsBranchBundle() || (hadExtension && strings.HasPrefix(ref, "../")) {
|
||||
baseDir = pi.Dir()
|
||||
} else {
|
||||
baseDir = pi.ContainerDir()
|
||||
}
|
||||
}
|
||||
|
||||
rel := path.Join(baseDir, inRef)
|
||||
rel := path.Join(baseDir, ref)
|
||||
|
||||
if !hadExtension && !paths.HasExt(rel) {
|
||||
// See comment above.
|
||||
rel += defaultContentExt
|
||||
}
|
||||
|
||||
relPath := contentPathParser.Parse(files.ComponentFolderContent, rel)
|
||||
relPath, _ := contentPathParser.ParseBaseAndBaseNameNoIdentifier(files.ComponentFolderContent, rel)
|
||||
|
||||
n, err := c.getContentNodeFromPath(relPath, ref)
|
||||
if n != nil || err != nil {
|
||||
@@ -199,9 +194,9 @@ func (c *pageFinder) getContentNodeForRef(context page.Page, isReflink, hadExten
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
refPath := contentPathParser.Parse(files.ComponentFolderContent, ref)
|
||||
relPath, nameNoIdentifier := contentPathParser.ParseBaseAndBaseNameNoIdentifier(files.ComponentFolderContent, ref)
|
||||
|
||||
n, err := c.getContentNodeFromPath(refPath, ref)
|
||||
n, err := c.getContentNodeFromPath(relPath, ref)
|
||||
|
||||
if n != nil || err != nil {
|
||||
return n, err
|
||||
@@ -225,7 +220,7 @@ func (c *pageFinder) getContentNodeForRef(context page.Page, isReflink, hadExten
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
n = c.pageMap.pageReverseIndex.Get(refPath.BaseNameNoIdentifier())
|
||||
n = c.pageMap.pageReverseIndex.Get(nameNoIdentifier)
|
||||
if n == ambiguousContentNode {
|
||||
return nil, fmt.Errorf("page reference %q is ambiguous", inRef)
|
||||
}
|
||||
@@ -243,7 +238,7 @@ func (c *pageFinder) getContentNodeFromRefReverseLookup(ref string, fi hugofs.Fi
|
||||
|
||||
realFilename := filepath.Join(dir, ref)
|
||||
|
||||
pcs, err := s.BaseFs.Content.ReverseLookup(realFilename)
|
||||
pcs, err := s.BaseFs.Content.ReverseLookup(realFilename, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -258,9 +253,7 @@ func (c *pageFinder) getContentNodeFromRefReverseLookup(ref string, fi hugofs.Fi
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *pageFinder) getContentNodeFromPath(refPath *paths.Path, ref string) (contentNodeI, error) {
|
||||
s := refPath.Base()
|
||||
|
||||
func (c *pageFinder) getContentNodeFromPath(s string, ref string) (contentNodeI, error) {
|
||||
n := c.pageMap.treePages.Get(s)
|
||||
if n != nil {
|
||||
return n, nil
|
||||
|
||||
@@ -386,6 +386,33 @@ Home. {{ with .Page.GetPage "p1.xyz" }}{{ else }}OK 1{{ end }} {{ with .Site.Get
|
||||
b.AssertFileContent("public/index.html", "Home. OK 1 OK 2")
|
||||
}
|
||||
|
||||
func TestGetPageIssue12120(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
|
||||
-- content/s1/p1/index.md --
|
||||
---
|
||||
title: p1
|
||||
layout: p1
|
||||
---
|
||||
-- content/s1/p2.md --
|
||||
---
|
||||
title: p2
|
||||
layout: p2
|
||||
---
|
||||
-- layouts/_default/p1.html --
|
||||
{{ (.GetPage "p2.md").Title }}|
|
||||
-- layouts/_default/p2.html --
|
||||
{{ (.GetPage "p1").Title }}|
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
b.AssertFileContent("public/s1/p1/index.html", "p2") // failing test
|
||||
b.AssertFileContent("public/s1/p2/index.html", "p1")
|
||||
}
|
||||
|
||||
func TestGetPageBundleToRegular(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
@@ -636,3 +663,33 @@ RegularPagesRecursive: {{ range .RegularPagesRecursive }}{{ .Kind }}:{{ .RelPerm
|
||||
|
||||
b.AssertFileContent("public/index.html", `RegularPagesRecursive: page:/p1/|page:/post/p2/||End.`)
|
||||
}
|
||||
|
||||
// Issue #12169.
|
||||
func TestPagesSimilarSectionNames(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
-- content/draftsection/_index.md --
|
||||
---
|
||||
draft: true
|
||||
---
|
||||
-- content/draftsection/sub/_index.md --got
|
||||
-- content/draftsection/sub/d1.md --
|
||||
-- content/s1/_index.md --
|
||||
-- content/s1/p1.md --
|
||||
-- content/s1-foo/_index.md --
|
||||
-- content/s1-foo/p2.md --
|
||||
-- content/s1-foo/s2/_index.md --
|
||||
-- content/s1-foo/s2/p3.md --
|
||||
-- content/s1-foo/s2-foo/_index.md --
|
||||
-- content/s1-foo/s2-foo/p4.md --
|
||||
-- layouts/_default/list.html --
|
||||
{{ .RelPermalink }}: Pages: {{ range .Pages }}{{ .RelPermalink }}|{{ end }}$
|
||||
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/index.html", "/: Pages: /s1-foo/|/s1/|$")
|
||||
b.AssertFileContent("public/s1/index.html", "/s1/: Pages: /s1/p1/|$")
|
||||
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/|$")
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ func (c *pagesCollector) Collect() (collectErr error) {
|
||||
// We always start from a directory.
|
||||
collectErr = c.collectDir(id.p, id.isDir, func(fim hugofs.FileMetaInfo) bool {
|
||||
if id.delete || id.isDir {
|
||||
if id.isDir {
|
||||
if id.isDir && fim.Meta().PathInfo.IsLeafBundle() {
|
||||
return strings.HasPrefix(fim.Meta().PathInfo.Path(), paths.AddTrailingSlash(id.p.Path()))
|
||||
}
|
||||
|
||||
@@ -195,9 +195,9 @@ func (c *pagesCollector) collectDir(dirPath *paths.Path, isDir bool, inFilter fu
|
||||
var dpath string
|
||||
if dirPath != nil {
|
||||
if isDir {
|
||||
dpath = filepath.FromSlash(dirPath.Path())
|
||||
dpath = filepath.FromSlash(dirPath.Unnormalized().Path())
|
||||
} else {
|
||||
dpath = filepath.FromSlash(dirPath.Dir())
|
||||
dpath = filepath.FromSlash(dirPath.Unnormalized().Dir())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+257
-4
@@ -77,6 +77,27 @@ func TestRebuildEditTextFileInLeafBundle(t *testing.T) {
|
||||
b.AssertRenderCountContent(1)
|
||||
}
|
||||
|
||||
func TestRebuiEditUnmarshaledYamlFileInLeafBundle(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableLiveReload = true
|
||||
disableKinds = ["taxonomy", "term", "sitemap", "robotsTXT", "404", "rss"]
|
||||
-- content/mybundle/index.md --
|
||||
-- content/mybundle/mydata.yml --
|
||||
foo: bar
|
||||
-- layouts/_default/single.html --
|
||||
MyData: {{ .Resources.Get "mydata.yml" | transform.Unmarshal }}|
|
||||
`
|
||||
b := TestRunning(t, files)
|
||||
|
||||
b.AssertFileContent("public/mybundle/index.html", "MyData: map[foo:bar]")
|
||||
|
||||
b.EditFileReplaceAll("content/mybundle/mydata.yml", "bar", "bar edited").Build()
|
||||
|
||||
b.AssertFileContent("public/mybundle/index.html", "MyData: map[foo:bar edited]")
|
||||
}
|
||||
|
||||
func TestRebuildEditTextFileInHomeBundle(t *testing.T) {
|
||||
b := TestRunning(t, rebuildFilesSimple)
|
||||
b.AssertFileContent("public/index.html", "Home Content.")
|
||||
@@ -261,6 +282,52 @@ func TestRebuilErrorRecovery(t *testing.T) {
|
||||
b.EditFileReplaceAll("content/mysection/mysectionbundle/index.md", "{{< foo }}", "{{< foo >}}").Build()
|
||||
}
|
||||
|
||||
func TestRebuildAddPageListPagesInHome(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableLiveReload = true
|
||||
-- content/asection/s1.md --
|
||||
-- content/p1.md --
|
||||
---
|
||||
title: "P1"
|
||||
weight: 1
|
||||
---
|
||||
-- layouts/_default/single.html --
|
||||
Single: {{ .Title }}|{{ .Content }}|
|
||||
-- layouts/index.html --
|
||||
Pages: {{ range .RegularPages }}{{ .RelPermalink }}|{{ end }}$
|
||||
`
|
||||
|
||||
b := TestRunning(t, files)
|
||||
b.AssertFileContent("public/index.html", "Pages: /p1/|$")
|
||||
b.AddFiles("content/p2.md", ``).Build()
|
||||
b.AssertFileContent("public/index.html", "Pages: /p1/|/p2/|$")
|
||||
}
|
||||
|
||||
func TestRebuildAddPageWithSpaceListPagesInHome(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableLiveReload = true
|
||||
-- content/asection/s1.md --
|
||||
-- content/p1.md --
|
||||
---
|
||||
title: "P1"
|
||||
weight: 1
|
||||
---
|
||||
-- layouts/_default/single.html --
|
||||
Single: {{ .Title }}|{{ .Content }}|
|
||||
-- layouts/index.html --
|
||||
Pages: {{ range .RegularPages }}{{ .RelPermalink }}|{{ end }}$
|
||||
`
|
||||
|
||||
b := TestRunning(t, files)
|
||||
b.AssertFileContent("public/index.html", "Pages: /p1/|$")
|
||||
b.AddFiles("content/test test/index.md", ``).Build()
|
||||
b.AssertFileContent("public/index.html", "Pages: /p1/|/test-test/|$")
|
||||
}
|
||||
|
||||
func TestRebuildScopedToOutputFormat(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
@@ -462,7 +529,7 @@ func TestRebuildVariations(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disdableKinds = ["term", "taxonomy"]
|
||||
disableKinds = ["term", "taxonomy"]
|
||||
disableLiveReload = true
|
||||
defaultContentLanguage = "nn"
|
||||
paginate = 20
|
||||
@@ -597,7 +664,7 @@ Codeblock Include: {{ .Title }}|
|
||||
// - Edit content file direct
|
||||
// - Edit content file transitive shortcode
|
||||
// - Edit content file transitive render hook
|
||||
// - Rename one languge version of a content file
|
||||
// - Rename one language version of a content file
|
||||
// - Delete content file, check site.RegularPages and section.RegularPagesRecursive (length)
|
||||
// - Add content file (see above).
|
||||
// - Edit shortcode
|
||||
@@ -1131,7 +1198,7 @@ Single.
|
||||
Running: true,
|
||||
NeedsOsFS: true,
|
||||
NeedsNpmInstall: true,
|
||||
// LogLevel: logg.LevelTrace,
|
||||
// LogLevel: logg.LevelDebug,
|
||||
},
|
||||
).Build()
|
||||
|
||||
@@ -1149,6 +1216,118 @@ Single.
|
||||
b.AssertFileContent("public/index.html", "Home.", "<style>body {\n\tbackground: blue;\n}</style>")
|
||||
}
|
||||
|
||||
func TestRebuildI18n(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableLiveReload = true
|
||||
-- i18n/en.toml --
|
||||
hello = "Hello"
|
||||
-- layouts/index.html --
|
||||
Hello: {{ i18n "hello" }}
|
||||
`
|
||||
|
||||
b := TestRunning(t, files)
|
||||
|
||||
b.AssertFileContent("public/index.html", "Hello: Hello")
|
||||
|
||||
b.EditFileReplaceAll("i18n/en.toml", "Hello", "Hugo").Build()
|
||||
|
||||
b.AssertFileContent("public/index.html", "Hello: Hugo")
|
||||
}
|
||||
|
||||
func TestRebuildEditContentNonDefaultLanguage(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableLiveReload = true
|
||||
defaultContentLanguage = "en"
|
||||
defaultContentLanguageInSubdir = true
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
[languages.nn]
|
||||
weight = 2
|
||||
-- content/p1/index.en.md --
|
||||
---
|
||||
title: "P1 en"
|
||||
---
|
||||
P1 en.
|
||||
-- content/p1/b.en.md --
|
||||
---
|
||||
title: "B en"
|
||||
---
|
||||
B en.
|
||||
-- content/p1/f1.en.txt --
|
||||
F1 en
|
||||
-- content/p1/index.nn.md --
|
||||
---
|
||||
title: "P1 nn"
|
||||
---
|
||||
P1 nn.
|
||||
-- content/p1/b.nn.md --
|
||||
---
|
||||
title: "B nn"
|
||||
---
|
||||
B nn.
|
||||
-- content/p1/f1.nn.txt --
|
||||
F1 nn
|
||||
-- layouts/_default/single.html --
|
||||
Single: {{ .Title }}|{{ .Content }}|Bundled File: {{ with .Resources.GetMatch "f1.*" }}{{ .Content }}{{ end }}|Bundled Page: {{ with .Resources.GetMatch "b.*" }}{{ .Content }}{{ end }}|
|
||||
`
|
||||
|
||||
b := TestRunning(t, files)
|
||||
|
||||
b.AssertFileContent("public/nn/p1/index.html", "Single: P1 nn|<p>P1 nn.</p>", "F1 nn|")
|
||||
b.EditFileReplaceAll("content/p1/index.nn.md", "P1 nn.", "P1 nn edit.").Build()
|
||||
b.AssertFileContent("public/nn/p1/index.html", "Single: P1 nn|<p>P1 nn edit.</p>\n|")
|
||||
b.EditFileReplaceAll("content/p1/f1.nn.txt", "F1 nn", "F1 nn edit.").Build()
|
||||
b.AssertFileContent("public/nn/p1/index.html", "Bundled File: F1 nn edit.")
|
||||
b.EditFileReplaceAll("content/p1/b.nn.md", "B nn.", "B nn edit.").Build()
|
||||
b.AssertFileContent("public/nn/p1/index.html", "B nn edit.")
|
||||
}
|
||||
|
||||
func TestRebuildEditContentNonDefaultLanguageDifferentBundles(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableLiveReload = true
|
||||
defaultContentLanguage = "en"
|
||||
defaultContentLanguageInSubdir = true
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
contentDir = "content/en"
|
||||
[languages.nn]
|
||||
weight = 2
|
||||
contentDir = "content/nn"
|
||||
-- content/en/p1en/index.md --
|
||||
---
|
||||
title: "P1 en"
|
||||
---
|
||||
-- content/nn/p1nn/index.md --
|
||||
---
|
||||
title: "P1 nn"
|
||||
---
|
||||
P1 nn.
|
||||
-- layouts/_default/single.html --
|
||||
Single: {{ .Title }}|{{ .Content }}|
|
||||
`
|
||||
|
||||
b := TestRunning(t, files)
|
||||
|
||||
b.AssertFileContent("public/nn/p1nn/index.html", "Single: P1 nn|<p>P1 nn.</p>")
|
||||
b.EditFileReplaceAll("content/nn/p1nn/index.md", "P1 nn.", "P1 nn edit.").Build()
|
||||
b.AssertFileContent("public/nn/p1nn/index.html", "Single: P1 nn|<p>P1 nn edit.</p>\n|")
|
||||
b.AssertFileContent("public/nn/p1nn/index.html", "P1 nn edit.")
|
||||
}
|
||||
|
||||
func TestRebuildVariationsAssetsSassImport(t *testing.T) {
|
||||
if !htesting.IsCI() {
|
||||
t.Skip("skip CI only")
|
||||
@@ -1209,7 +1388,7 @@ func benchmarkFilesEdit(count int) string {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disdableKinds = ["term", "taxonomy"]
|
||||
disableKinds = ["term", "taxonomy"]
|
||||
disableLiveReload = true
|
||||
-- layouts/_default/single.html --
|
||||
Single: {{ .Title }}|{{ .Content }}|
|
||||
@@ -1261,3 +1440,77 @@ func BenchmarkRebuildContentFileChange(b *testing.B) {
|
||||
// fmt.Println(bb.LogString())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRebuildConcat(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableLiveReload = true
|
||||
disableKinds = ["taxonomy", "term", "sitemap", "robotsTXT", "404", "rss"]
|
||||
-- assets/a.css --
|
||||
a
|
||||
-- assets/b.css --
|
||||
b
|
||||
-- assets/c.css --
|
||||
c
|
||||
-- assets/common/c1.css --
|
||||
c1
|
||||
-- assets/common/c2.css --
|
||||
c2
|
||||
-- layouts/index.html --
|
||||
{{ $a := resources.Get "a.css" }}
|
||||
{{ $b := resources.Get "b.css" }}
|
||||
{{ $common := resources.Match "common/*.css" | resources.Concat "common.css" | minify }}
|
||||
{{ $ab := slice $a $b $common | resources.Concat "ab.css" }}
|
||||
all: {{ $ab.RelPermalink }}
|
||||
`
|
||||
b := TestRunning(t, files)
|
||||
|
||||
b.AssertFileContent("public/ab.css", "abc1c2")
|
||||
b.EditFileReplaceAll("assets/common/c2.css", "c2", "c2 edited").Build()
|
||||
b.AssertFileContent("public/ab.css", "abc1c2 edited")
|
||||
b.AddFiles("assets/common/c3.css", "c3").Build()
|
||||
b.AssertFileContent("public/ab.css", "abc1c2 editedc3")
|
||||
}
|
||||
|
||||
func TestRebuildEditArchetypeFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableLiveReload = true
|
||||
-- archetypes/default.md --
|
||||
---
|
||||
title: "Default"
|
||||
---
|
||||
`
|
||||
|
||||
b := TestRunning(t, files)
|
||||
// Just make sure that it doesn't panic.
|
||||
b.EditFileReplaceAll("archetypes/default.md", "Default", "Default Edited").Build()
|
||||
}
|
||||
|
||||
func TestRebuildEditMixedCaseTemplateFileIssue12165(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableLiveReload = true
|
||||
-- layouts/partials/MyTemplate.html --
|
||||
MyTemplate
|
||||
-- layouts/index.html --
|
||||
MyTemplate: {{ partial "MyTemplate.html" . }}|
|
||||
|
||||
|
||||
`
|
||||
|
||||
b := TestRunning(t, files)
|
||||
|
||||
b.AssertFileContent("public/index.html", "MyTemplate: MyTemplate")
|
||||
|
||||
b.EditFileReplaceAll("layouts/partials/MyTemplate.html", "MyTemplate", "MyTemplate Edited").Build()
|
||||
|
||||
b.AssertFileContent("public/index.html", "MyTemplate: MyTemplate Edited")
|
||||
}
|
||||
|
||||
@@ -39,3 +39,16 @@ func TestRobotsTXTOutput(t *testing.T) {
|
||||
|
||||
b.AssertFileContent("public/robots.txt", "User-agent: Googlebot")
|
||||
}
|
||||
|
||||
func TestRobotsTXTDefaultTemplate(t *testing.T) {
|
||||
t.Parallel()
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "http://auth/bub/"
|
||||
enableRobotsTXT = true
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/robots.txt", "User-agent: *")
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user