mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-27 16:58:53 +00:00
Compare commits
232 Commits
v0.123.0
...
fix/miscpoll
| Author | SHA1 | Date | |
|---|---|---|---|
| 6389adcc90 | |||
| bc05d854b2 | |||
| be47830a87 | |||
| 447108fed2 | |||
| c71e24af51 | |||
| 44f1edcb06 | |||
| 917199a94e | |||
| c8dac67def | |||
| 0068f03290 | |||
| 0221ddb39e | |||
| 74b9b8a229 | |||
| 420f26b6bb | |||
| 312b71232d | |||
| 1464091ad6 | |||
| 2c88e454d8 | |||
| eaa42a8754 | |||
| 245928a1ff | |||
| 519f41dbd7 | |||
| 7f3061723e | |||
| 931e096f21 | |||
| 548dc21378 | |||
| b893a09aa6 | |||
| 6b006616e5 | |||
| 3d40aba512 | |||
| 39cf906bc0 | |||
| 1aacfced39 | |||
| 32c967551b | |||
| 266140251f | |||
| 74ab839ccb | |||
| 92290aa892 | |||
| 6dbbe6dd3a | |||
| e2d66e3218 | |||
| 55dea41c1a | |||
| 87ab7f7ffc | |||
| ee26e69ce3 | |||
| 6e83d00a83 | |||
| d02f0622b4 | |||
| 6dfeb9f038 | |||
| ca9a77ef92 | |||
| b1bf0bff2c | |||
| b1d808bc37 | |||
| 3c6260f046 | |||
| 70c13f444e | |||
| c46d603a02 | |||
| 69ede10edc | |||
| bb59a7ed97 | |||
| 503d20954f | |||
| 68e95327f7 | |||
| 9cd7db61d3 | |||
| c892e75fbc | |||
| 4255d13d3e | |||
| c8b9f9f81c | |||
| 7be7f89bf6 | |||
| c8e400b621 | |||
| 9dd687027f | |||
| 1961327536 | |||
| cc3574ef4f | |||
| fe84cc218e | |||
| babcb339a8 | |||
| 7203a95a60 | |||
| fb084390cd | |||
| fb51b698b3 | |||
| 6b867972ec | |||
| 509ab08c1b | |||
| 2d75f539e1 | |||
| 15a4b9b337 | |||
| 10a8448eee | |||
| 722c486a34 | |||
| f40f50ead0 | |||
| 4e483f5d4a | |||
| 06d248910c | |||
| 004b694390 | |||
| da6112fc65 | |||
| faf9fedc3d | |||
| 11aa893198 | |||
| d88cb5269a | |||
| 68c5ad638c | |||
| 0c188fda24 | |||
| bbc6888d02 | |||
| 8c14d1edc3 | |||
| a32400b5f4 | |||
| df9f2fb617 | |||
| fa60a2fbc3 | |||
| fe63de3a83 | |||
| e197c7b29d | |||
| 74e9129568 | |||
| df11327ba9 | |||
| a18e2bcb9a | |||
| 6049ba99f0 | |||
| 8e50ccfae7 | |||
| 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 |
@@ -4,7 +4,7 @@ parameters:
|
||||
defaults: &defaults
|
||||
resource_class: large
|
||||
docker:
|
||||
- image: bepsays/ci-hugoreleaser:1.22200.20000
|
||||
- image: bepsays/ci-hugoreleaser:1.22200.20201
|
||||
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.20201
|
||||
steps:
|
||||
- *restore-cache
|
||||
- &attach-workspace
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
name: TestDartSassV1
|
||||
env:
|
||||
GOPROXY: https://proxy.golang.org
|
||||
GO111MODULE: on
|
||||
DART_SASS_VERSION: 1.62.1
|
||||
DART_SASS_SHA_LINUX: 3574da75a7322a539034648b8ff84ff2cca162eb924d72b663d718cd3936f075
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.22.x]
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- 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: |
|
||||
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
|
||||
+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
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2022 The Hugo Authors.
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -62,8 +62,7 @@ And with [Hugo Modules], you can share content, assets, data, translations, them
|
||||
|
||||
<p> </p>
|
||||
<p float="left">
|
||||
<a href="https://www.linode.com/?utm_campaign=hugosponsor&utm_medium=banner&utm_source=hugogithub" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/linode-logo_standard_light_medium.png" width="200" alt="Linode"></a>
|
||||
<a href="https://cloudcannon.com/hugo-cms/?utm_campaign=HugoSponsorship&utm_source=sponsor&utm_content=gohugo" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/cloudcannon-blue.svg" width="220" alt="CloudCannon"></a>
|
||||
<a href="https://www.linode.com/?utm_campaign=hugosponsor&utm_medium=banner&utm_source=hugogithub" target="_blank"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/assets/images/sponsors/linode-logo_standard_light_medium.png" width="200" alt="Linode"></a>
|
||||
<p> </p>
|
||||
|
||||
## Installation
|
||||
|
||||
+1
-1
@@ -4,4 +4,4 @@
|
||||
|
||||
Please report (suspected) security vulnerabilities to **[bjorn.erik.pedersen@gmail.com](mailto:bjorn.erik.pedersen@gmail.com)**. You will receive a response from us within 48 hours. If we can confirm the issue, we will release a patch as soon as possible depending on the complexity of the issue but historically within days.
|
||||
|
||||
Also see [Hugo's Security Model](https://gohugo.io/about/security-model/).
|
||||
Also see [Hugo's Security Model](https://gohugo.io/about/security/).
|
||||
|
||||
Vendored
+47
-11
@@ -67,7 +67,7 @@ func New(opts Options) *Cache {
|
||||
evictedIdentities := collections.NewStack[identity.Identity]()
|
||||
|
||||
onEvict := func(k, v any) {
|
||||
if !opts.Running {
|
||||
if !opts.Watching {
|
||||
return
|
||||
}
|
||||
identity.WalkIdentitiesShallow(v, func(level int, id identity.Identity) bool {
|
||||
@@ -97,7 +97,7 @@ type Options struct {
|
||||
CheckInterval time.Duration
|
||||
MaxSize int
|
||||
MinMaxSize int
|
||||
Running bool
|
||||
Watching bool
|
||||
}
|
||||
|
||||
// Options for a partition.
|
||||
@@ -140,16 +140,25 @@ func (c *Cache) DrainEvictedIdentities() []identity.Identity {
|
||||
}
|
||||
|
||||
// ClearMatching clears all partition for which the predicate returns true.
|
||||
func (c *Cache) ClearMatching(predicate func(k, v any) bool) {
|
||||
func (c *Cache) ClearMatching(predicatePartition func(k string, p PartitionManager) bool, predicateValue func(k, v any) bool) {
|
||||
if predicatePartition == nil {
|
||||
predicatePartition = func(k string, p PartitionManager) bool { return true }
|
||||
}
|
||||
if predicateValue == nil {
|
||||
panic("nil predicateValue")
|
||||
}
|
||||
g := rungroup.Run[PartitionManager](context.Background(), rungroup.Config[PartitionManager]{
|
||||
NumWorkers: len(c.partitions),
|
||||
Handle: func(ctx context.Context, partition PartitionManager) error {
|
||||
partition.clearMatching(predicate)
|
||||
partition.clearMatching(predicateValue)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
for _, p := range c.partitions {
|
||||
for k, p := range c.partitions {
|
||||
if !predicatePartition(k, p) {
|
||||
continue
|
||||
}
|
||||
g.Enqueue(p)
|
||||
}
|
||||
|
||||
@@ -295,6 +304,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 +349,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)
|
||||
@@ -354,6 +365,7 @@ func GetOrCreatePartition[K comparable, V any](c *Cache, name string, opts Optio
|
||||
trace: c.opts.Log.Logger().WithLevel(logg.LevelTrace).WithField("partition", name),
|
||||
opts: opts,
|
||||
}
|
||||
|
||||
c.partitions[name] = partition
|
||||
|
||||
return partition
|
||||
@@ -373,13 +385,37 @@ type Partition[K comparable, V any] struct {
|
||||
|
||||
// GetOrCreate gets or creates a value for the given key.
|
||||
func (p *Partition[K, V]) GetOrCreate(key K, create func(key K) (V, error)) (V, error) {
|
||||
v, err := p.doGetOrCreate(key, create)
|
||||
if err != nil {
|
||||
return p.zero, err
|
||||
}
|
||||
if resource.StaleVersion(v) > 0 {
|
||||
p.c.Delete(key)
|
||||
return p.doGetOrCreate(key, create)
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (p *Partition[K, V]) doGetOrCreate(key K, create func(key K) (V, error)) (V, error) {
|
||||
v, _, err := p.c.GetOrCreate(key, create)
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (p *Partition[K, V]) GetOrCreateWitTimeout(key K, duration time.Duration, create func(key K) (V, error)) (V, error) {
|
||||
v, err := p.doGetOrCreateWitTimeout(key, duration, create)
|
||||
if err != nil {
|
||||
return p.zero, err
|
||||
}
|
||||
if resource.StaleVersion(v) > 0 {
|
||||
p.c.Delete(key)
|
||||
return p.doGetOrCreateWitTimeout(key, duration, create)
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
// GetOrCreateWitTimeout gets or creates a value for the given key and times out if the create function
|
||||
// takes too long.
|
||||
func (p *Partition[K, V]) GetOrCreateWitTimeout(key K, duration time.Duration, create func(key K) (V, error)) (V, error) {
|
||||
func (p *Partition[K, V]) doGetOrCreateWitTimeout(key K, duration time.Duration, create func(key K) (V, error)) (V, error) {
|
||||
resultch := make(chan V, 1)
|
||||
errch := make(chan error, 1)
|
||||
|
||||
@@ -436,7 +472,7 @@ func (p *Partition[K, V]) clearOnRebuild(changeset ...identity.Identity) {
|
||||
|
||||
shouldDelete := func(key K, v V) bool {
|
||||
// We always clear elements marked as stale.
|
||||
if resource.IsStaleAny(v) {
|
||||
if resource.StaleVersion(v) > 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -491,8 +527,8 @@ func (p *Partition[K, V]) Keys() []K {
|
||||
|
||||
func (p *Partition[K, V]) clearStale() {
|
||||
p.c.DeleteFunc(func(key K, v V) bool {
|
||||
isStale := resource.IsStaleAny(v)
|
||||
if isStale {
|
||||
staleVersion := resource.StaleVersion(v)
|
||||
if staleVersion > 0 {
|
||||
p.trace.Log(
|
||||
logg.StringFunc(
|
||||
func() string {
|
||||
@@ -502,7 +538,7 @@ func (p *Partition[K, V]) clearStale() {
|
||||
)
|
||||
}
|
||||
|
||||
return isStale
|
||||
return staleVersion > 0
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Vendored
+7
-7
@@ -29,12 +29,12 @@ var (
|
||||
)
|
||||
|
||||
type testItem struct {
|
||||
name string
|
||||
isStale bool
|
||||
name string
|
||||
staleVersion uint32
|
||||
}
|
||||
|
||||
func (t testItem) IsStale() bool {
|
||||
return t.isStale
|
||||
func (t testItem) StaleVersion() uint32 {
|
||||
return t.staleVersion
|
||||
}
|
||||
|
||||
func (t testItem) IdentifierBase() string {
|
||||
@@ -109,7 +109,7 @@ func newTestCache(t *testing.T) *Cache {
|
||||
|
||||
p2.GetOrCreate("clearBecauseStale", func(string) (testItem, error) {
|
||||
return testItem{
|
||||
isStale: true,
|
||||
staleVersion: 32,
|
||||
}, nil
|
||||
})
|
||||
|
||||
@@ -121,7 +121,7 @@ func newTestCache(t *testing.T) *Cache {
|
||||
|
||||
p2.GetOrCreate("clearNever", func(string) (testItem, error) {
|
||||
return testItem{
|
||||
isStale: false,
|
||||
staleVersion: 0,
|
||||
}, nil
|
||||
})
|
||||
|
||||
@@ -156,7 +156,7 @@ func TestClear(t *testing.T) {
|
||||
|
||||
cache = newTestCache(t)
|
||||
|
||||
cache.ClearMatching(func(k, v any) bool {
|
||||
cache.ClearMatching(nil, func(k, v any) bool {
|
||||
return k.(string) == "clearOnRebuild"
|
||||
})
|
||||
|
||||
|
||||
Vendored
+109
-13
@@ -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.
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/httpcache"
|
||||
"github.com/gohugoio/hugo/common/hugio"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
|
||||
@@ -182,6 +183,15 @@ func (c *Cache) ReadOrCreate(id string,
|
||||
return
|
||||
}
|
||||
|
||||
// NamedLock locks the given id. The lock is released when the returned function is called.
|
||||
func (c *Cache) NamedLock(id string) func() {
|
||||
id = cleanID(id)
|
||||
c.nlocker.Lock(id)
|
||||
return func() {
|
||||
c.nlocker.Unlock(id)
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrCreate tries to get the file with the given id from cache. If not found or expired, create will
|
||||
// be invoked and the result cached.
|
||||
// This method is protected by a named lock using the given id as identifier.
|
||||
@@ -218,7 +228,23 @@ func (c *Cache) GetOrCreate(id string, create func() (io.ReadCloser, error)) (It
|
||||
var buff bytes.Buffer
|
||||
return info,
|
||||
hugio.ToReadCloser(&buff),
|
||||
afero.WriteReader(c.Fs, id, io.TeeReader(r, &buff))
|
||||
c.writeReader(id, io.TeeReader(r, &buff))
|
||||
}
|
||||
|
||||
func (c *Cache) writeReader(id string, r io.Reader) error {
|
||||
dir := filepath.Dir(id)
|
||||
if dir != "" {
|
||||
_ = c.Fs.MkdirAll(dir, 0o777)
|
||||
}
|
||||
f, err := c.Fs.Create(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, _ = io.Copy(f, r)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOrCreateBytes is the same as GetOrCreate, but produces a byte slice.
|
||||
@@ -253,9 +279,10 @@ func (c *Cache) GetOrCreateBytes(id string, create func() ([]byte, error)) (Item
|
||||
return info, b, nil
|
||||
}
|
||||
|
||||
if err := afero.WriteReader(c.Fs, id, bytes.NewReader(b)); err != nil {
|
||||
if err := c.writeReader(id, bytes.NewReader(b)); err != nil {
|
||||
return info, nil, err
|
||||
}
|
||||
|
||||
return info, b, nil
|
||||
}
|
||||
|
||||
@@ -305,16 +332,8 @@ func (c *Cache) getOrRemove(id string) hugio.ReadSeekCloser {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.maxAge > 0 {
|
||||
fi, err := c.Fs.Stat(id)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.isExpired(fi.ModTime()) {
|
||||
c.Fs.Remove(id)
|
||||
return nil
|
||||
}
|
||||
if removed, err := c.removeIfExpired(id); err != nil || removed {
|
||||
return nil
|
||||
}
|
||||
|
||||
f, err := c.Fs.Open(id)
|
||||
@@ -325,6 +344,49 @@ func (c *Cache) getOrRemove(id string) hugio.ReadSeekCloser {
|
||||
return f
|
||||
}
|
||||
|
||||
func (c *Cache) getBytesAndRemoveIfExpired(id string) ([]byte, bool) {
|
||||
if c.maxAge == 0 {
|
||||
// No caching.
|
||||
return nil, false
|
||||
}
|
||||
|
||||
f, err := c.Fs.Open(id)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
b, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
removed, err := c.removeIfExpired(id)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return b, removed
|
||||
}
|
||||
|
||||
func (c *Cache) removeIfExpired(id string) (bool, error) {
|
||||
if c.maxAge <= 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
fi, err := c.Fs.Stat(id)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if c.isExpired(fi.ModTime()) {
|
||||
c.Fs.Remove(id)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (c *Cache) isExpired(modTime time.Time) bool {
|
||||
if c.maxAge < 0 {
|
||||
return false
|
||||
@@ -398,3 +460,37 @@ func NewCaches(p *helpers.PathSpec) (Caches, error) {
|
||||
func cleanID(name string) string {
|
||||
return strings.TrimPrefix(filepath.Clean(name), helpers.FilePathSeparator)
|
||||
}
|
||||
|
||||
// AsHTTPCache returns an httpcache.Cache implementation for this file cache.
|
||||
// Note that none of the methods are protected by named locks, so you need to make sure
|
||||
// to do that in your own code.
|
||||
func (c *Cache) AsHTTPCache() httpcache.Cache {
|
||||
return &httpCache{c: c}
|
||||
}
|
||||
|
||||
type httpCache struct {
|
||||
c *Cache
|
||||
}
|
||||
|
||||
func (h *httpCache) Get(id string) (resp []byte, ok bool) {
|
||||
id = cleanID(id)
|
||||
b, removed := h.c.getBytesAndRemoveIfExpired(id)
|
||||
|
||||
return b, !removed
|
||||
}
|
||||
|
||||
func (h *httpCache) Set(id string, resp []byte) {
|
||||
if h.c.maxAge == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
id = cleanID(id)
|
||||
|
||||
if err := h.c.writeReader(id, bytes.NewReader(resp)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *httpCache) Delete(key string) {
|
||||
h.c.Fs.Remove(key)
|
||||
}
|
||||
|
||||
Vendored
+208
@@ -0,0 +1,208 @@
|
||||
// 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 httpcache
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/gohugoio/hugo/common/predicate"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// DefaultConfig holds the default configuration for the HTTP cache.
|
||||
var DefaultConfig = Config{
|
||||
Cache: Cache{
|
||||
For: GlobMatcher{
|
||||
Excludes: []string{"**"},
|
||||
},
|
||||
},
|
||||
Polls: []PollConfig{
|
||||
{
|
||||
For: GlobMatcher{
|
||||
Includes: []string{"**"},
|
||||
},
|
||||
Disable: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Config holds the configuration for the HTTP cache.
|
||||
type Config struct {
|
||||
// Configures the HTTP cache behaviour (RFC 9111).
|
||||
// When this is not enabled for a resource, Hugo will go straight to the file cache.
|
||||
Cache Cache
|
||||
|
||||
// Polls holds a list of configurations for polling remote resources to detect changes in watch mode.
|
||||
// This can be disabled for some resources, typically if they are known to not change.
|
||||
Polls []PollConfig
|
||||
}
|
||||
|
||||
type Cache struct {
|
||||
// Enable HTTP cache behaviour (RFC 9111) for these rsources.
|
||||
For GlobMatcher
|
||||
}
|
||||
|
||||
func (c *Config) Compile() (ConfigCompiled, error) {
|
||||
var cc ConfigCompiled
|
||||
|
||||
p, err := c.Cache.For.CompilePredicate()
|
||||
if err != nil {
|
||||
return cc, err
|
||||
}
|
||||
|
||||
cc.For = p
|
||||
|
||||
for _, pc := range c.Polls {
|
||||
|
||||
p, err := pc.For.CompilePredicate()
|
||||
if err != nil {
|
||||
return cc, err
|
||||
}
|
||||
|
||||
cc.PollConfigs = append(cc.PollConfigs, PollConfigCompiled{
|
||||
For: p,
|
||||
Config: pc,
|
||||
})
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
// PollConfig holds the configuration for polling remote resources to detect changes in watch mode.
|
||||
// TODO1 make sure this enabled only in watch mode.
|
||||
type PollConfig struct {
|
||||
// What remote resources to apply this configuration to.
|
||||
For GlobMatcher
|
||||
|
||||
// Disable polling for this configuration.
|
||||
Disable bool
|
||||
|
||||
// Low is the lower bound for the polling interval.
|
||||
// This is the starting point when the resource has recently changed,
|
||||
// if that resource stops changing, the polling interval will gradually increase towards High.
|
||||
Low time.Duration
|
||||
|
||||
// High is the upper bound for the polling interval.
|
||||
// This is the interval used when the resource is stable.
|
||||
High time.Duration
|
||||
}
|
||||
|
||||
func (c PollConfig) MarshalJSON() (b []byte, err error) {
|
||||
// Marshal the durations as strings.
|
||||
type Alias PollConfig
|
||||
return json.Marshal(&struct {
|
||||
Low string
|
||||
High string
|
||||
Alias
|
||||
}{
|
||||
Low: c.Low.String(),
|
||||
High: c.High.String(),
|
||||
Alias: (Alias)(c),
|
||||
})
|
||||
}
|
||||
|
||||
type GlobMatcher struct {
|
||||
// Excludes holds a list of glob patterns that will be excluded.
|
||||
Excludes []string
|
||||
|
||||
// Includes holds a list of glob patterns that will be included.
|
||||
Includes []string
|
||||
}
|
||||
|
||||
type ConfigCompiled struct {
|
||||
For predicate.P[string]
|
||||
PollConfigs []PollConfigCompiled
|
||||
}
|
||||
|
||||
func (c *ConfigCompiled) PollConfigFor(s string) PollConfigCompiled {
|
||||
for _, pc := range c.PollConfigs {
|
||||
if pc.For(s) {
|
||||
return pc
|
||||
}
|
||||
}
|
||||
return PollConfigCompiled{}
|
||||
}
|
||||
|
||||
func (c *ConfigCompiled) IsPollingDisabled() bool {
|
||||
for _, pc := range c.PollConfigs {
|
||||
if !pc.Config.Disable {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type PollConfigCompiled struct {
|
||||
For predicate.P[string]
|
||||
Config PollConfig
|
||||
}
|
||||
|
||||
func (p PollConfigCompiled) IsZero() bool {
|
||||
return p.For == nil
|
||||
}
|
||||
|
||||
func (gm *GlobMatcher) CompilePredicate() (func(string) bool, error) {
|
||||
var p predicate.P[string]
|
||||
for _, include := range gm.Includes {
|
||||
g, err := glob.Compile(include, '/')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fn := func(s string) bool {
|
||||
return g.Match(s)
|
||||
}
|
||||
p = p.Or(fn)
|
||||
}
|
||||
|
||||
for _, exclude := range gm.Excludes {
|
||||
g, err := glob.Compile(exclude, '/')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fn := func(s string) bool {
|
||||
return !g.Match(s)
|
||||
}
|
||||
p = p.And(fn)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func DecodeConfig(bcfg config.BaseConfig, m map[string]any) (Config, error) {
|
||||
if len(m) == 0 {
|
||||
return DefaultConfig, nil
|
||||
}
|
||||
|
||||
var c Config
|
||||
|
||||
dc := &mapstructure.DecoderConfig{
|
||||
Result: &c,
|
||||
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
|
||||
WeaklyTypedInput: true,
|
||||
}
|
||||
|
||||
decoder, err := mapstructure.NewDecoder(dc)
|
||||
if err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
||||
if err := decoder.Decode(m); err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
// 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 httpcache_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
)
|
||||
|
||||
func TestConfigCustom(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
[httpcache]
|
||||
[httpcache.cache.for]
|
||||
includes = ["**gohugo.io**"]
|
||||
[[httpcache.polls]]
|
||||
low = "5s"
|
||||
high = "32s"
|
||||
[httpcache.polls.for]
|
||||
includes = ["**gohugo.io**"]
|
||||
|
||||
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
httpcacheConf := b.H.Configs.Base.HTTPCache
|
||||
compiled := b.H.Configs.Base.C.HTTPCache
|
||||
|
||||
b.Assert(httpcacheConf.Cache.For.Includes, qt.DeepEquals, []string{"**gohugo.io**"})
|
||||
b.Assert(httpcacheConf.Cache.For.Excludes, qt.IsNil)
|
||||
|
||||
pc := compiled.PollConfigFor("https://gohugo.io/foo.jpg")
|
||||
b.Assert(pc.Config.Low, qt.Equals, 5*time.Second)
|
||||
b.Assert(pc.Config.High, qt.Equals, 32*time.Second)
|
||||
b.Assert(compiled.PollConfigFor("https://example.com/foo.jpg").IsZero(), qt.IsTrue)
|
||||
}
|
||||
|
||||
func TestConfigDefault(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
`
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
compiled := b.H.Configs.Base.C.HTTPCache
|
||||
|
||||
b.Assert(compiled.For("https://gohugo.io/posts.json"), qt.IsFalse)
|
||||
b.Assert(compiled.For("https://gohugo.io/foo.jpg"), qt.IsFalse)
|
||||
b.Assert(compiled.PollConfigFor("https://gohugo.io/foo.jpg").Config.Disable, qt.IsTrue)
|
||||
}
|
||||
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
// 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 httpcache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestGlobMatcher(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
g := GlobMatcher{
|
||||
Includes: []string{"**/*.jpg", "**.png", "**/bar/**"},
|
||||
Excludes: []string{"**/foo.jpg", "**.css"},
|
||||
}
|
||||
|
||||
p, err := g.CompilePredicate()
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
c.Assert(p("foo.jpg"), qt.IsFalse)
|
||||
c.Assert(p("foo.png"), qt.IsTrue)
|
||||
c.Assert(p("foo/bar.jpg"), qt.IsTrue)
|
||||
c.Assert(p("foo/bar.png"), qt.IsTrue)
|
||||
c.Assert(p("foo/bar/foo.jpg"), qt.IsFalse)
|
||||
c.Assert(p("foo/bar/foo.css"), qt.IsFalse)
|
||||
c.Assert(p("foo.css"), qt.IsFalse)
|
||||
c.Assert(p("foo/bar/foo.css"), qt.IsFalse)
|
||||
c.Assert(p("foo/bar/foo.xml"), qt.IsTrue)
|
||||
}
|
||||
+62
-40
@@ -48,6 +48,8 @@ import (
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/resources/kinds"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -102,6 +104,9 @@ type rootCommand struct {
|
||||
commonConfigs *lazycache.Cache[int32, *commonConfig]
|
||||
hugoSites *lazycache.Cache[int32, *hugolib.HugoSites]
|
||||
|
||||
// changesFromBuild received from Hugo in watch mode.
|
||||
changesFromBuild chan []identity.Identity
|
||||
|
||||
commands []simplecobra.Commander
|
||||
|
||||
// Flags
|
||||
@@ -127,6 +132,7 @@ type rootCommand struct {
|
||||
verbose bool
|
||||
debug bool
|
||||
quiet bool
|
||||
devMode bool // Hidden flag.
|
||||
|
||||
renderToMemory bool
|
||||
|
||||
@@ -302,7 +308,7 @@ func (r *rootCommand) ConfigFromProvider(key int32, cfg config.Provider) (*commo
|
||||
|
||||
func (r *rootCommand) HugFromConfig(conf *commonConfig) (*hugolib.HugoSites, error) {
|
||||
h, _, err := r.hugoSites.GetOrCreate(r.configVersionID.Load(), func(key int32) (*hugolib.HugoSites, error) {
|
||||
depsCfg := deps.DepsCfg{Configs: conf.configs, Fs: conf.fs, LogOut: r.logger.Out(), LogLevel: r.logger.Level()}
|
||||
depsCfg := r.newDepsConfig(conf)
|
||||
return hugolib.NewHugoSites(depsCfg)
|
||||
})
|
||||
return h, err
|
||||
@@ -314,23 +320,27 @@ func (r *rootCommand) Hugo(cfg config.Provider) (*hugolib.HugoSites, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
depsCfg := deps.DepsCfg{Configs: conf.configs, Fs: conf.fs, LogOut: r.logger.Out(), LogLevel: r.logger.Level()}
|
||||
depsCfg := r.newDepsConfig(conf)
|
||||
return hugolib.NewHugoSites(depsCfg)
|
||||
})
|
||||
return h, err
|
||||
}
|
||||
|
||||
func (r *rootCommand) newDepsConfig(conf *commonConfig) deps.DepsCfg {
|
||||
return deps.DepsCfg{Configs: conf.configs, Fs: conf.fs, LogOut: r.logger.Out(), LogLevel: r.logger.Level(), ChangesFromBuild: r.changesFromBuild}
|
||||
}
|
||||
|
||||
func (r *rootCommand) Name() string {
|
||||
return "hugo"
|
||||
}
|
||||
|
||||
func (r *rootCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
|
||||
if !r.buildWatch {
|
||||
defer r.timeTrack(time.Now(), "Total")
|
||||
}
|
||||
|
||||
b := newHugoBuilder(r, nil)
|
||||
|
||||
if !r.buildWatch {
|
||||
defer b.postBuild("Total", time.Now())
|
||||
}
|
||||
|
||||
if err := b.loadConfig(cd, false); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -406,6 +416,8 @@ func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
r.changesFromBuild = make(chan []identity.Identity, 10)
|
||||
|
||||
r.commonConfigs = lazycache.New(lazycache.Options[int32, *commonConfig]{MaxEntries: 5})
|
||||
// We don't want to keep stale HugoSites in memory longer than needed.
|
||||
r.hugoSites = lazycache.New(lazycache.Options[int32, *hugolib.HugoSites]{
|
||||
@@ -422,29 +434,33 @@ func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
|
||||
func (r *rootCommand) createLogger(running bool) (loggers.Logger, error) {
|
||||
level := logg.LevelWarn
|
||||
|
||||
if r.logLevel != "" {
|
||||
switch strings.ToLower(r.logLevel) {
|
||||
case "debug":
|
||||
level = logg.LevelDebug
|
||||
case "info":
|
||||
level = logg.LevelInfo
|
||||
case "warn", "warning":
|
||||
level = logg.LevelWarn
|
||||
case "error":
|
||||
level = logg.LevelError
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid log level: %q, must be one of debug, warn, info or error", r.logLevel)
|
||||
}
|
||||
if r.devMode {
|
||||
level = logg.LevelTrace
|
||||
} else {
|
||||
if r.verbose {
|
||||
hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0")
|
||||
hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0")
|
||||
level = logg.LevelInfo
|
||||
}
|
||||
if r.logLevel != "" {
|
||||
switch strings.ToLower(r.logLevel) {
|
||||
case "debug":
|
||||
level = logg.LevelDebug
|
||||
case "info":
|
||||
level = logg.LevelInfo
|
||||
case "warn", "warning":
|
||||
level = logg.LevelWarn
|
||||
case "error":
|
||||
level = logg.LevelError
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid log level: %q, must be one of debug, warn, info or error", r.logLevel)
|
||||
}
|
||||
} else {
|
||||
if r.verbose {
|
||||
hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0")
|
||||
hugo.Deprecate("--verbose", "use --logLevel info", "v0.114.0")
|
||||
level = logg.LevelInfo
|
||||
}
|
||||
|
||||
if r.debug {
|
||||
hugo.Deprecate("--debug", "use --logLevel debug", "v0.114.0")
|
||||
level = logg.LevelDebug
|
||||
if r.debug {
|
||||
hugo.Deprecate("--debug", "use --logLevel debug", "v0.114.0")
|
||||
level = logg.LevelDebug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,46 +498,50 @@ 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.renderToMemory, "renderToMemory", "M", false, "render to memory (mostly useful when running the server)")
|
||||
|
||||
cmd.PersistentFlags().BoolVarP(&r.verbose, "verbose", "v", false, "verbose output")
|
||||
cmd.PersistentFlags().BoolVarP(&r.debug, "debug", "", false, "debug output")
|
||||
cmd.PersistentFlags().BoolVarP(&r.devMode, "devMode", "", false, "only used for internal testing, flag hidden.")
|
||||
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")
|
||||
|
||||
cmd.PersistentFlags().MarkHidden("devMode")
|
||||
|
||||
// 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 +554,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 +580,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
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+22
-11
@@ -45,9 +45,10 @@ func newGenCommand() *genCommand {
|
||||
genmandir string
|
||||
|
||||
// Chroma flags.
|
||||
style string
|
||||
highlightStyle string
|
||||
linesStyle string
|
||||
style string
|
||||
highlightStyle string
|
||||
lineNumbersInlineStyle string
|
||||
lineNumbersTableStyle string
|
||||
)
|
||||
|
||||
newChromaStyles := func() simplecobra.Commander {
|
||||
@@ -63,8 +64,11 @@ See https://xyproto.github.io/splash/docs/all.html for a preview of the availabl
|
||||
if highlightStyle != "" {
|
||||
builder.Add(chroma.LineHighlight, highlightStyle)
|
||||
}
|
||||
if linesStyle != "" {
|
||||
builder.Add(chroma.LineNumbers, linesStyle)
|
||||
if lineNumbersInlineStyle != "" {
|
||||
builder.Add(chroma.LineNumbers, lineNumbersInlineStyle)
|
||||
}
|
||||
if lineNumbersTableStyle != "" {
|
||||
builder.Add(chroma.LineNumbersTable, lineNumbersTableStyle)
|
||||
}
|
||||
style, err := builder.Build()
|
||||
if err != nil {
|
||||
@@ -75,9 +79,15 @@ 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.PersistentFlags().StringVar(&highlightStyle, "highlightStyle", "", "style used for highlighting lines (see https://github.com/alecthomas/chroma)")
|
||||
cmd.PersistentFlags().StringVar(&linesStyle, "linesStyle", "", "style used for line numbers (see https://github.com/alecthomas/chroma)")
|
||||
_ = cmd.RegisterFlagCompletionFunc("style", cobra.NoFileCompletions)
|
||||
cmd.PersistentFlags().StringVar(&highlightStyle, "highlightStyle", "", `foreground and background colors for highlighted lines, e.g. --highlightStyle "#fff000 bg:#000fff"`)
|
||||
_ = cmd.RegisterFlagCompletionFunc("highlightStyle", cobra.NoFileCompletions)
|
||||
cmd.PersistentFlags().StringVar(&lineNumbersInlineStyle, "lineNumbersInlineStyle", "", `foreground and background colors for inline line numbers, e.g. --lineNumbersInlineStyle "#fff000 bg:#000fff"`)
|
||||
_ = cmd.RegisterFlagCompletionFunc("lineNumbersInlineStyle", cobra.NoFileCompletions)
|
||||
cmd.PersistentFlags().StringVar(&lineNumbersTableStyle, "lineNumbersTableStyle", "", `foreground and background colors for table line numbers, e.g. --lineNumbersTableStyle "#fff000 bg:#000fff"`)
|
||||
_ = cmd.RegisterFlagCompletionFunc("lineNumbersTableStyle", cobra.NoFileCompletions)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -115,9 +125,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 +182,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 +237,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")
|
||||
},
|
||||
}
|
||||
|
||||
+50
-3
@@ -43,6 +43,7 @@ import (
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
"github.com/gohugoio/hugo/hugolib/filesystems"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/livereload"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
"github.com/gohugoio/hugo/watcher"
|
||||
@@ -75,9 +76,14 @@ type hugoBuilder struct {
|
||||
errState hugoBuilderErrState
|
||||
}
|
||||
|
||||
var errConfigNotSet = errors.New("config not set")
|
||||
|
||||
func (c *hugoBuilder) withConfE(fn func(conf *commonConfig) error) error {
|
||||
c.confmu.Lock()
|
||||
defer c.confmu.Unlock()
|
||||
if c.conf == nil {
|
||||
return errConfigNotSet
|
||||
}
|
||||
return fn(c.conf)
|
||||
}
|
||||
|
||||
@@ -338,6 +344,27 @@ func (c *hugoBuilder) newWatcher(pollIntervalStr string, dirList ...string) (*wa
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case changes := <-c.r.changesFromBuild:
|
||||
c.errState.setBuildErr(nil)
|
||||
unlock, err := h.LockBuild()
|
||||
if err != nil {
|
||||
c.r.logger.Errorln("Failed to acquire a build lock: %s", err)
|
||||
return
|
||||
}
|
||||
c.changeDetector.PrepareNew()
|
||||
err = c.rebuildSitesForChanges(changes)
|
||||
if err != nil {
|
||||
c.r.logger.Errorln("Error while watching:", err)
|
||||
}
|
||||
if c.s != nil && c.s.doLiveReload {
|
||||
doReload := c.changeDetector == nil || len(c.changeDetector.changed()) > 0
|
||||
doReload = doReload || c.showErrorInBrowser && c.errCount() > 0
|
||||
if doReload {
|
||||
livereload.ForceRefresh()
|
||||
}
|
||||
}
|
||||
unlock()
|
||||
|
||||
case evs := <-watcher.Events:
|
||||
unlock, err := h.LockBuild()
|
||||
if err != nil {
|
||||
@@ -585,7 +612,7 @@ func (c *hugoBuilder) fullRebuild(changeType string) {
|
||||
time.Sleep(2 * time.Second)
|
||||
}()
|
||||
|
||||
defer c.r.timeTrack(time.Now(), "Rebuilt")
|
||||
defer c.postBuild("Rebuilt", time.Now())
|
||||
|
||||
err := c.reloadConfig()
|
||||
if err != nil {
|
||||
@@ -849,13 +876,13 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
|
||||
h.BaseFs.SourceFilesystems,
|
||||
dynamicEvents)
|
||||
|
||||
onePageName := pickOneWriteOrCreatePath(partitionedEvents.ContentEvents)
|
||||
onePageName := pickOneWriteOrCreatePath(h.Conf.ContentTypes(), partitionedEvents.ContentEvents)
|
||||
|
||||
c.printChangeDetected("")
|
||||
c.changeDetector.PrepareNew()
|
||||
|
||||
func() {
|
||||
defer c.r.timeTrack(time.Now(), "Total")
|
||||
defer c.postBuild("Total", time.Now())
|
||||
if err := c.rebuildSites(dynamicEvents); err != nil {
|
||||
c.handleBuildErr(err, "Rebuild failed")
|
||||
}
|
||||
@@ -901,6 +928,13 @@ func (c *hugoBuilder) handleEvents(watcher *watcher.Batcher,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *hugoBuilder) postBuild(what string, start time.Time) {
|
||||
if h, err := c.hugo(); err == nil && h.Conf.Running() {
|
||||
h.LogServerAddresses()
|
||||
}
|
||||
c.r.timeTrack(start, what)
|
||||
}
|
||||
|
||||
func (c *hugoBuilder) hugo() (*hugolib.HugoSites, error) {
|
||||
var h *hugolib.HugoSites
|
||||
if err := c.withConfE(func(conf *commonConfig) error {
|
||||
@@ -1007,6 +1041,19 @@ func (c *hugoBuilder) rebuildSites(events []fsnotify.Event) error {
|
||||
return h.Build(hugolib.BuildCfg{NoBuildLock: true, RecentlyVisited: c.visitedURLs, ErrRecovery: c.errState.wasErr()}, events...)
|
||||
}
|
||||
|
||||
func (c *hugoBuilder) rebuildSitesForChanges(ids []identity.Identity) error {
|
||||
c.errState.setBuildErr(nil)
|
||||
h, err := c.hugo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
whatChanged := &hugolib.WhatChanged{}
|
||||
whatChanged.Add(ids...)
|
||||
err = h.Build(hugolib.BuildCfg{NoBuildLock: true, WhatChanged: whatChanged, RecentlyVisited: c.visitedURLs, ErrRecovery: c.errState.wasErr()})
|
||||
c.errState.setBuildErr(err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *hugoBuilder) reloadConfig() error {
|
||||
c.r.Reset()
|
||||
c.r.configVersionID.Add(1)
|
||||
|
||||
@@ -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")
|
||||
},
|
||||
},
|
||||
|
||||
+39
-11
@@ -26,6 +26,7 @@ import (
|
||||
"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.
|
||||
@@ -40,6 +41,8 @@ func newListCommand() *listCommand {
|
||||
p.PublishDate().Format(time.RFC3339),
|
||||
strconv.FormatBool(p.Draft()),
|
||||
p.Permalink(),
|
||||
p.Kind(),
|
||||
p.Section(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +69,8 @@ func newListCommand() *listCommand {
|
||||
"publishDate",
|
||||
"draft",
|
||||
"permalink",
|
||||
"kind",
|
||||
"section",
|
||||
})
|
||||
|
||||
for _, p := range h.Pages() {
|
||||
@@ -74,9 +79,6 @@ func newListCommand() *listCommand {
|
||||
if err := writer.Write(record); err != nil {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,8 +89,8 @@ func newListCommand() *listCommand {
|
||||
commands: []simplecobra.Commander{
|
||||
&simpleCommand{
|
||||
name: "drafts",
|
||||
short: "List all drafts",
|
||||
long: `List all of the drafts in your content directory.`,
|
||||
short: "List draft content",
|
||||
long: `List draft content.`,
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
shouldInclude := func(p page.Page) bool {
|
||||
if !p.Draft() || p.File() == nil {
|
||||
@@ -102,11 +104,14 @@ func newListCommand() *listCommand {
|
||||
"buildExpired", true,
|
||||
)
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
},
|
||||
},
|
||||
&simpleCommand{
|
||||
name: "future",
|
||||
short: "List all posts dated in the future",
|
||||
long: `List all of the posts in your content directory which will be posted in the future.`,
|
||||
short: "List future content",
|
||||
long: `List content with a future publication date.`,
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
shouldInclude := func(p page.Page) bool {
|
||||
if !resource.IsFuture(p) || p.File() == nil {
|
||||
@@ -119,11 +124,14 @@ func newListCommand() *listCommand {
|
||||
"buildDrafts", true,
|
||||
)
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
},
|
||||
},
|
||||
&simpleCommand{
|
||||
name: "expired",
|
||||
short: "List all posts already expired",
|
||||
long: `List all of the posts in your content directory which has already expired.`,
|
||||
short: "List expired content",
|
||||
long: `List content with a past expiration date.`,
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
shouldInclude := func(p page.Page) bool {
|
||||
if !resource.IsExpired(p) || p.File() == nil {
|
||||
@@ -136,17 +144,37 @@ func newListCommand() *listCommand {
|
||||
"buildDrafts", true,
|
||||
)
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
},
|
||||
},
|
||||
&simpleCommand{
|
||||
name: "all",
|
||||
short: "List all posts",
|
||||
long: `List all of the posts in your content directory, include drafts, future and expired pages.`,
|
||||
short: "List all content",
|
||||
long: `List all content including draft, future, and expired.`,
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
shouldInclude := func(p page.Page) bool {
|
||||
return p.File() != nil
|
||||
}
|
||||
return list(cd, r, shouldInclude, "buildDrafts", true, "buildFuture", true, "buildExpired", true)
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
},
|
||||
},
|
||||
&simpleCommand{
|
||||
name: "published",
|
||||
short: "List published content",
|
||||
long: `List content that is not draft, future, or expired.`,
|
||||
run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
|
||||
shouldInclude := func(p page.Page) bool {
|
||||
return !p.Draft() && !resource.IsFuture(p) && !resource.IsExpired(p) && p.File() != nil
|
||||
}
|
||||
return list(cd, r, shouldInclude)
|
||||
},
|
||||
withc: func(cmd *cobra.Command, r *rootCommand) {
|
||||
cmd.ValidArgsFunction = cobra.NoFileCompletions
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+18
-1
@@ -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
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -192,7 +209,7 @@ func (c *newCommand) newSiteNextStepsText(path string, format string) string {
|
||||
1. Change the current directory to ` + path + `.
|
||||
2. Create or install a theme:
|
||||
- Create a new theme with the command "hugo new theme <THEMENAME>"
|
||||
- Install a theme from https://themes.gohugo.io/
|
||||
- Or, install a theme from https://themes.gohugo.io/
|
||||
3. Edit hugo.` + format + `, setting the "theme" property to the theme name.
|
||||
4. Create new content with the command "hugo new content `)
|
||||
|
||||
|
||||
@@ -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))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+27
-25
@@ -46,12 +46,12 @@ import (
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/common/urls"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
"github.com/gohugoio/hugo/hugolib/filesystems"
|
||||
"github.com/gohugoio/hugo/livereload"
|
||||
@@ -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,24 +523,26 @@ 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")
|
||||
cmd.Flags().BoolVar(&c.noHTTPCache, "noHTTPCache", false, "prevent HTTP caching")
|
||||
cmd.Flags().BoolVarP(&c.serverAppend, "appendPort", "", true, "append port to baseURL")
|
||||
cmd.Flags().BoolVar(&c.disableLiveReload, "disableLiveReload", false, "watch without enabling live browser reload on rebuild")
|
||||
cmd.Flags().BoolVar(&c.navigateToChanged, "navigateToChanged", false, "navigate to changed content file on live browser reload")
|
||||
cmd.Flags().BoolVarP(&c.navigateToChanged, "navigateToChanged", "N", false, "navigate to changed content file on live browser reload")
|
||||
cmd.Flags().BoolVar(&c.renderStaticToDisk, "renderStaticToDisk", false, "serve static files from disk and dynamic files from memory")
|
||||
cmd.Flags().BoolVar(&c.disableFastRender, "disableFastRender", false, "enables full re-renders on changes")
|
||||
cmd.Flags().BoolVar(&c.disableBrowserError, "disableBrowserError", false, "do not show build errors in the browser")
|
||||
|
||||
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.
|
||||
@@ -1186,16 +1188,16 @@ func partitionDynamicEvents(sourceFs *filesystems.SourceFilesystems, events []fs
|
||||
return
|
||||
}
|
||||
|
||||
func pickOneWriteOrCreatePath(events []fsnotify.Event) string {
|
||||
func pickOneWriteOrCreatePath(contentTypes config.ContentTypesProvider, events []fsnotify.Event) string {
|
||||
name := ""
|
||||
|
||||
for _, ev := range events {
|
||||
if ev.Op&fsnotify.Write == fsnotify.Write || ev.Op&fsnotify.Create == fsnotify.Create {
|
||||
if files.IsIndexContentFile(ev.Name) {
|
||||
if contentTypes.IsIndexContentFile(ev.Name) {
|
||||
return ev.Name
|
||||
}
|
||||
|
||||
if files.IsContentFile(ev.Name) {
|
||||
if contentTypes.IsContentFile(ev.Name) {
|
||||
name = ev.Name
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +123,20 @@ func InSlicEqualFold(arr []string, el string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ToString converts the given value to a string.
|
||||
// Note that this is a more strict version compared to cast.ToString,
|
||||
// as it will not try to convert numeric values to strings,
|
||||
// but only accept strings or fmt.Stringer.
|
||||
func ToString(v any) (string, bool) {
|
||||
switch vv := v.(type) {
|
||||
case string:
|
||||
return vv, true
|
||||
case fmt.Stringer:
|
||||
return vv.String(), true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
type Tuple struct {
|
||||
First string
|
||||
Second string
|
||||
|
||||
@@ -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: 127,
|
||||
PatchLevel: 0,
|
||||
Suffix: "-DEV",
|
||||
}
|
||||
|
||||
@@ -27,7 +27,12 @@ func NewCache[K comparable, T any]() *Cache[K, T] {
|
||||
}
|
||||
|
||||
// Delete deletes the given key from the cache.
|
||||
// If c is nil, this method is a no-op.
|
||||
func (c *Cache[K, T]) Get(key K) (T, bool) {
|
||||
if c == nil {
|
||||
var zero T
|
||||
return zero, false
|
||||
}
|
||||
c.RLock()
|
||||
v, found := c.m[key]
|
||||
c.RUnlock()
|
||||
@@ -60,6 +65,15 @@ func (c *Cache[K, T]) Set(key K, value T) {
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
// ForEeach calls the given function for each key/value pair in the cache.
|
||||
func (c *Cache[K, T]) ForEeach(f func(K, T)) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
for k, v := range c.m {
|
||||
f(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
// SliceCache is a simple thread safe cache backed by a map.
|
||||
type SliceCache[T any] struct {
|
||||
m map[string][]T
|
||||
|
||||
+4
-4
@@ -112,17 +112,17 @@ func ToSliceStringMap(in any) ([]map[string]any, error) {
|
||||
}
|
||||
|
||||
// LookupEqualFold finds key in m with case insensitive equality checks.
|
||||
func LookupEqualFold[T any | string](m map[string]T, key string) (T, bool) {
|
||||
func LookupEqualFold[T any | string](m map[string]T, key string) (T, string, bool) {
|
||||
if v, found := m[key]; found {
|
||||
return v, true
|
||||
return v, key, true
|
||||
}
|
||||
for k, v := range m {
|
||||
if strings.EqualFold(k, key) {
|
||||
return v, true
|
||||
return v, k, true
|
||||
}
|
||||
}
|
||||
var s T
|
||||
return s, false
|
||||
return s, "", false
|
||||
}
|
||||
|
||||
// MergeShallow merges src into dst, but only if the key does not already exist in dst.
|
||||
|
||||
@@ -180,16 +180,18 @@ func TestLookupEqualFold(t *testing.T) {
|
||||
"B": "bv",
|
||||
}
|
||||
|
||||
v, found := LookupEqualFold(m1, "b")
|
||||
v, k, found := LookupEqualFold(m1, "b")
|
||||
c.Assert(found, qt.IsTrue)
|
||||
c.Assert(v, qt.Equals, "bv")
|
||||
c.Assert(k, qt.Equals, "B")
|
||||
|
||||
m2 := map[string]string{
|
||||
"a": "av",
|
||||
"B": "bv",
|
||||
}
|
||||
|
||||
v, found = LookupEqualFold(m2, "b")
|
||||
v, k, found = LookupEqualFold(m2, "b")
|
||||
c.Assert(found, qt.IsTrue)
|
||||
c.Assert(k, qt.Equals, "B")
|
||||
c.Assert(v, qt.Equals, "bv")
|
||||
}
|
||||
|
||||
+55
-36
@@ -25,8 +25,6 @@ import (
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
)
|
||||
|
||||
var defaultPathParser PathParser
|
||||
|
||||
// PathParser parses a path into a Path.
|
||||
type PathParser struct {
|
||||
// Maps the language code to its index in the languages/sites slice.
|
||||
@@ -34,11 +32,9 @@ type PathParser struct {
|
||||
|
||||
// Reports whether the given language is disabled.
|
||||
IsLangDisabled func(string) bool
|
||||
}
|
||||
|
||||
// Parse parses component c with path s into Path using the default path parser.
|
||||
func Parse(c, s string) *Path {
|
||||
return defaultPathParser.Parse(c, s)
|
||||
// Reports whether the given ext is a content file.
|
||||
IsContentExt func(string) bool
|
||||
}
|
||||
|
||||
// NormalizePathString returns a normalized path string using the very basic Hugo rules.
|
||||
@@ -54,15 +50,27 @@ func NormalizePathStringBasic(s string) string {
|
||||
|
||||
// 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
|
||||
defer putPath(p)
|
||||
p, err := pp.doParse(c, s, p)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return identity.StringIdentity(p.IdentifierBase())
|
||||
return p
|
||||
}
|
||||
|
||||
// Parse parses component c with path s into Path using Hugo's content path rules.
|
||||
@@ -96,7 +104,6 @@ func (pp *PathParser) parse(component, s string) (*Path, error) {
|
||||
var err error
|
||||
// Preserve the original case for titles etc.
|
||||
p.unnormalized, err = pp.doParse(component, s, pp.newPath(component))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -183,23 +190,26 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
|
||||
}
|
||||
}
|
||||
|
||||
isContentComponent := p.component == files.ComponentFolderContent || p.component == files.ComponentFolderArchetypes
|
||||
isContent := isContentComponent && files.IsContentExt(p.Ext())
|
||||
|
||||
if isContent {
|
||||
if len(p.identifiers) > 0 {
|
||||
isContentComponent := p.component == files.ComponentFolderContent || p.component == files.ComponentFolderArchetypes
|
||||
isContent := isContentComponent && pp.IsContentExt(p.Ext())
|
||||
id := p.identifiers[len(p.identifiers)-1]
|
||||
b := p.s[p.posContainerHigh : id.Low-1]
|
||||
switch b {
|
||||
case "index":
|
||||
p.bundleType = PathTypeLeaf
|
||||
case "_index":
|
||||
p.bundleType = PathTypeBranch
|
||||
default:
|
||||
p.bundleType = PathTypeContentSingle
|
||||
}
|
||||
if isContent {
|
||||
switch b {
|
||||
case "index":
|
||||
p.bundleType = PathTypeLeaf
|
||||
case "_index":
|
||||
p.bundleType = PathTypeBranch
|
||||
default:
|
||||
p.bundleType = PathTypeContentSingle
|
||||
}
|
||||
|
||||
if slashCount == 2 && p.IsLeafBundle() {
|
||||
p.posSectionHigh = 0
|
||||
if slashCount == 2 && p.IsLeafBundle() {
|
||||
p.posSectionHigh = 0
|
||||
}
|
||||
} else if b == files.NameContentData && files.IsContentDataExt(p.Ext()) {
|
||||
p.bundleType = PathTypeContentData
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,13 +237,16 @@ 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
|
||||
|
||||
// Branch bundles, e.g. /blog/_index.md
|
||||
PathTypeBranch
|
||||
|
||||
// Content data file, _content.gotmpl.
|
||||
PathTypeContentData
|
||||
)
|
||||
|
||||
type Path struct {
|
||||
@@ -259,7 +272,9 @@ type Path struct {
|
||||
|
||||
var pathPool = &sync.Pool{
|
||||
New: func() any {
|
||||
return &Path{}
|
||||
p := &Path{}
|
||||
p.reset()
|
||||
return p
|
||||
},
|
||||
}
|
||||
|
||||
@@ -268,6 +283,11 @@ func getPath() *Path {
|
||||
}
|
||||
|
||||
func putPath(p *Path) {
|
||||
p.reset()
|
||||
pathPool.Put(p)
|
||||
}
|
||||
|
||||
func (p *Path) reset() {
|
||||
p.s = ""
|
||||
p.posContainerLow = -1
|
||||
p.posContainerHigh = -1
|
||||
@@ -279,7 +299,6 @@ func putPath(p *Path) {
|
||||
p.disabled = false
|
||||
p.trimLeadingSlash = false
|
||||
p.unnormalized = nil
|
||||
pathPool.Put(p)
|
||||
}
|
||||
|
||||
// TrimLeadingSlash returns a copy of the Path with the leading slash removed.
|
||||
@@ -295,7 +314,7 @@ 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()
|
||||
}
|
||||
@@ -350,7 +369,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]
|
||||
@@ -358,7 +377,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 {
|
||||
@@ -368,7 +387,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() {
|
||||
@@ -377,7 +396,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]
|
||||
@@ -417,7 +436,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, "/") {
|
||||
@@ -503,10 +522,6 @@ func (p *Path) Identifiers() []string {
|
||||
return ids
|
||||
}
|
||||
|
||||
func (p *Path) IsHTML() bool {
|
||||
return files.IsHTML(p.Ext())
|
||||
}
|
||||
|
||||
func (p *Path) BundleType() PathType {
|
||||
return p.bundleType
|
||||
}
|
||||
@@ -523,6 +538,10 @@ func (p *Path) IsLeafBundle() bool {
|
||||
return p.bundleType == PathTypeLeaf
|
||||
}
|
||||
|
||||
func (p *Path) IsContentData() bool {
|
||||
return p.bundleType == PathTypeContentData
|
||||
}
|
||||
|
||||
func (p Path) ForBundleType(t PathType) *Path {
|
||||
p.bundleType = t
|
||||
return &p
|
||||
|
||||
@@ -27,6 +27,9 @@ var testParser = &PathParser{
|
||||
"no": 0,
|
||||
"en": 1,
|
||||
},
|
||||
IsContentExt: func(ext string) bool {
|
||||
return ext == "md"
|
||||
},
|
||||
}
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
@@ -333,6 +336,22 @@ func TestParse(t *testing.T) {
|
||||
c.Assert(p.Path(), qt.Equals, "/a/b/c.txt")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Content data file gotmpl",
|
||||
"/a/b/_content.gotmpl",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.Path(), qt.Equals, "/a/b/_content.gotmpl")
|
||||
c.Assert(p.Ext(), qt.Equals, "gotmpl")
|
||||
c.Assert(p.IsContentData(), qt.IsTrue)
|
||||
},
|
||||
},
|
||||
{
|
||||
"Content data file yaml",
|
||||
"/a/b/_content.yaml",
|
||||
func(c *qt.C, p *Path) {
|
||||
c.Assert(p.IsContentData(), qt.IsFalse)
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
c.Run(test.name, func(c *qt.C) {
|
||||
|
||||
@@ -24,6 +24,9 @@ func (p P[T]) And(ps ...P[T]) P[T] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if p == nil {
|
||||
return true
|
||||
}
|
||||
return p(v)
|
||||
}
|
||||
}
|
||||
@@ -36,6 +39,9 @@ func (p P[T]) Or(ps ...P[T]) P[T] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if p == nil {
|
||||
return false
|
||||
}
|
||||
return p(v)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
// 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 tasks
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RunEvery runs a function at intervals defined by the function itself.
|
||||
// Functions can be added and removed while running.
|
||||
type RunEvery struct {
|
||||
// Any error returned from the function will be passed to this function.
|
||||
HandleError func(string, error)
|
||||
|
||||
// If set, the function will be run immediately.
|
||||
RunImmediately bool
|
||||
|
||||
// The named functions to run.
|
||||
funcs map[string]*Func
|
||||
|
||||
mu sync.Mutex
|
||||
started bool
|
||||
closed bool
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
type Func struct {
|
||||
// The shortest interval between each run.
|
||||
IntervalLow time.Duration
|
||||
|
||||
// The longest interval between each run.
|
||||
IntervalHigh time.Duration
|
||||
|
||||
// The function to run.
|
||||
F func(interval time.Duration) (time.Duration, error)
|
||||
|
||||
interval time.Duration
|
||||
last time.Time
|
||||
}
|
||||
|
||||
func (r *RunEvery) Start() error {
|
||||
if r.started {
|
||||
return nil
|
||||
}
|
||||
|
||||
r.started = true
|
||||
r.quit = make(chan struct{})
|
||||
|
||||
go func() {
|
||||
if r.RunImmediately {
|
||||
r.run()
|
||||
}
|
||||
ticker := time.NewTicker(500 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-r.quit:
|
||||
return
|
||||
case <-ticker.C:
|
||||
r.run()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close stops the RunEvery from running.
|
||||
func (r *RunEvery) Close() error {
|
||||
if r.closed {
|
||||
return nil
|
||||
}
|
||||
r.closed = true
|
||||
if r.quit != nil {
|
||||
close(r.quit)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add adds a function to the RunEvery.
|
||||
func (r *RunEvery) Add(name string, f Func) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.funcs == nil {
|
||||
r.funcs = make(map[string]*Func)
|
||||
}
|
||||
if f.IntervalLow == 0 {
|
||||
f.IntervalLow = 500 * time.Millisecond
|
||||
}
|
||||
if f.IntervalHigh <= f.IntervalLow {
|
||||
f.IntervalHigh = 20 * time.Second
|
||||
}
|
||||
|
||||
start := f.IntervalHigh / 3
|
||||
if start < f.IntervalLow {
|
||||
start = f.IntervalLow
|
||||
}
|
||||
f.interval = start
|
||||
f.last = time.Now()
|
||||
|
||||
r.funcs[name] = &f
|
||||
}
|
||||
|
||||
// Remove removes a function from the RunEvery.
|
||||
func (r *RunEvery) Remove(name string) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
delete(r.funcs, name)
|
||||
}
|
||||
|
||||
// Has returns whether the RunEvery has a function with the given name.
|
||||
func (r *RunEvery) Has(name string) bool {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
_, found := r.funcs[name]
|
||||
return found
|
||||
}
|
||||
|
||||
func (r *RunEvery) run() {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
for name, f := range r.funcs {
|
||||
if time.Now().Before(f.last.Add(f.interval)) {
|
||||
continue
|
||||
}
|
||||
f.last = time.Now()
|
||||
interval, err := f.F(f.interval)
|
||||
if err != nil && r.HandleError != nil {
|
||||
r.HandleError(name, err)
|
||||
}
|
||||
|
||||
if interval < f.IntervalLow {
|
||||
interval = f.IntervalLow
|
||||
}
|
||||
|
||||
if interval > f.IntervalHigh {
|
||||
interval = f.IntervalHigh
|
||||
}
|
||||
f.interval = interval
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// 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 types
|
||||
|
||||
import "sync"
|
||||
|
||||
type Closer interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
type CloseAdder interface {
|
||||
Add(Closer)
|
||||
}
|
||||
|
||||
type Closers struct {
|
||||
mu sync.Mutex
|
||||
cs []Closer
|
||||
}
|
||||
|
||||
func (cs *Closers) Add(c Closer) {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
cs.cs = append(cs.cs, c)
|
||||
}
|
||||
|
||||
func (cs *Closers) Close() error {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
for _, c := range cs.cs {
|
||||
c.Close()
|
||||
}
|
||||
|
||||
cs.cs = cs.cs[:0]
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/cache/filecache"
|
||||
"github.com/gohugoio/hugo/cache/httpcache"
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
@@ -39,6 +40,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 +105,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.
|
||||
@@ -116,6 +120,10 @@ type Config struct {
|
||||
// <docsmeta>{"identifiers": ["caches"] }</docsmeta>
|
||||
Caches filecache.Configs `mapstructure:"-"`
|
||||
|
||||
// The httpcache configuration section contains HTTP-cache-related configuration options.
|
||||
// <docsmeta>{"identifiers": ["httpcache"] }</docsmeta>
|
||||
HTTPCache httpcache.Config `mapstructure:"-"`
|
||||
|
||||
// The markup configuration section contains markup-related configuration options.
|
||||
// <docsmeta>{"identifiers": ["markup"] }</docsmeta>
|
||||
Markup markup_config.Config `mapstructure:"-"`
|
||||
@@ -137,6 +145,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:"-"`
|
||||
@@ -353,6 +364,11 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
|
||||
}
|
||||
}
|
||||
|
||||
httpCache, err := c.HTTPCache.Compile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.C = &ConfigCompiled{
|
||||
Timeout: timeout,
|
||||
BaseURL: baseURL,
|
||||
@@ -361,11 +377,14 @@ func (c *Config) CompileConfig(logger loggers.Logger) error {
|
||||
DisabledLanguages: disabledLangs,
|
||||
IgnoredLogs: ignoredLogIDs,
|
||||
KindOutputFormats: kindOutputFormats,
|
||||
ContentTypes: media.DefaultContentTypes.FromTypes(c.MediaTypes.Config),
|
||||
CreateTitle: helpers.GetTitleFunc(c.TitleCaseStyle),
|
||||
IsUglyURLSection: isUglyURL,
|
||||
IgnoreFile: ignoreFile,
|
||||
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,
|
||||
HTTPCache: httpCache,
|
||||
transientErr: transientErr,
|
||||
}
|
||||
|
||||
@@ -393,15 +412,19 @@ type ConfigCompiled struct {
|
||||
Timeout time.Duration
|
||||
BaseURL urls.BaseURL
|
||||
BaseURLLiveReload urls.BaseURL
|
||||
ServerInterface string
|
||||
KindOutputFormats map[string]output.Formats
|
||||
ContentTypes media.ContentTypes
|
||||
DisabledKinds map[string]bool
|
||||
DisabledLanguages map[string]bool
|
||||
IgnoredLogs map[string]bool
|
||||
CreateTitle func(s string) string
|
||||
IsUglyURLSection func(section string) bool
|
||||
IgnoreFile func(filename string) bool
|
||||
SegmentFilter segments.SegmentFilter
|
||||
MainSections []string
|
||||
Clock time.Time
|
||||
HTTPCache httpcache.ConfigCompiled
|
||||
|
||||
// This is set to the last transient error found during config compilation.
|
||||
// With themes/modules we compute the configuration in multiple passes, and
|
||||
@@ -426,9 +449,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 +496,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 +566,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
|
||||
@@ -668,6 +699,13 @@ type Configs struct {
|
||||
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 +773,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, IsContentExt: c.Base.C.ContentTypes.IsContentSuffix}
|
||||
|
||||
c.configLangs = make([]config.AllProvider, len(c.Languages))
|
||||
for i, l := range c.LanguagesDefaultFirst {
|
||||
@@ -816,7 +854,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 +891,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 +941,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 +971,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 +1004,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)
|
||||
}
|
||||
|
||||
@@ -84,3 +84,21 @@ logPathWarnings = true
|
||||
b.Assert(conf.PrintI18nWarnings, qt.Equals, true)
|
||||
b.Assert(conf.PrintPathWarnings, qt.Equals, true)
|
||||
}
|
||||
|
||||
func TestRedefineContentTypes(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
[mediaTypes]
|
||||
[mediaTypes."text/html"]
|
||||
suffixes = ["html", "xhtml"]
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
conf := b.H.Configs.Base
|
||||
contentTypes := conf.C.ContentTypes
|
||||
|
||||
b.Assert(contentTypes.HTML.Suffixes(), qt.DeepEquals, []string{"html", "xhtml"})
|
||||
b.Assert(contentTypes.Markdown.Suffixes(), qt.DeepEquals, []string{"md", "mdown", "markdown"})
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/cache/filecache"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
|
||||
"github.com/gohugoio/hugo/cache/httpcache"
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
@@ -26,11 +27,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 +46,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 {
|
||||
@@ -96,6 +98,18 @@ var allDecoderSetups = map[string]decodeWeight{
|
||||
return err
|
||||
},
|
||||
},
|
||||
"httpcache": {
|
||||
key: "httpcache",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
var err error
|
||||
p.c.HTTPCache, err = httpcache.DecodeConfig(p.bcfg, p.p.GetStringMap(d.key))
|
||||
if p.c.IgnoreCache {
|
||||
p.c.HTTPCache.Cache.For.Excludes = []string{"**"}
|
||||
p.c.HTTPCache.Cache.For.Includes = []string{}
|
||||
}
|
||||
return err
|
||||
},
|
||||
},
|
||||
"build": {
|
||||
key: "build",
|
||||
decode: func(d decodeWeight, p decodeConfig) error {
|
||||
@@ -122,6 +136,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 +315,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
|
||||
},
|
||||
},
|
||||
|
||||
@@ -52,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
|
||||
@@ -71,6 +71,9 @@ func (c ConfigLanguage) Environment() string {
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) IsMultihost() bool {
|
||||
if len(c.m.Languages)-len(c.config.C.DisabledLanguages) <= 1 {
|
||||
return false
|
||||
}
|
||||
return c.m.IsMultihost
|
||||
}
|
||||
|
||||
@@ -78,7 +81,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
|
||||
}
|
||||
|
||||
@@ -141,6 +144,10 @@ func (c ConfigLanguage) NewIdentityManager(name string) identity.Manager {
|
||||
return identity.NewManager(name)
|
||||
}
|
||||
|
||||
func (c ConfigLanguage) ContentTypes() config.ContentTypesProvider {
|
||||
return c.config.C.ContentTypes
|
||||
}
|
||||
|
||||
// GetConfigSection is mostly used in tests. The switch statement isn't complete, but what's in use.
|
||||
func (c ConfigLanguage) GetConfigSection(s string) any {
|
||||
switch s {
|
||||
@@ -166,6 +173,8 @@ func (c ConfigLanguage) GetConfigSection(s string) any {
|
||||
return c.m.Modules
|
||||
case "deployment":
|
||||
return c.config.Deployment
|
||||
case "httpCacheCompiled":
|
||||
return c.config.C.HTTPCache
|
||||
default:
|
||||
panic("not implemented: " + s)
|
||||
}
|
||||
|
||||
@@ -189,6 +189,7 @@ func (l configLoader) applyDefaultConfig() error {
|
||||
"menus": maps.Params{},
|
||||
"disableLiveReload": false,
|
||||
"pluralizeListTitles": true,
|
||||
"capitalizeListTitles": true,
|
||||
"forceSyncStatic": false,
|
||||
"footnoteAnchorPrefix": "",
|
||||
"footnoteReturnLinkContents": "",
|
||||
|
||||
@@ -98,10 +98,6 @@ type BuildConfig struct {
|
||||
// One of never, fallback, always. Default is fallback
|
||||
UseResourceCacheWhen string
|
||||
|
||||
// When enabled, will duplicate bundled resource files across languages that
|
||||
// doesn't have a translated version.
|
||||
DuplicateResourceFiles bool
|
||||
|
||||
// When enabled, will collect and write a hugo_stats.json with some build
|
||||
// related aggregated data (e.g. CSS class names).
|
||||
// Note that this was a bool <= v0.115.0.
|
||||
@@ -215,6 +211,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) {
|
||||
|
||||
@@ -35,12 +35,13 @@ type AllProvider interface {
|
||||
PathParser() *paths.PathParser
|
||||
Environment() string
|
||||
IsMultihost() bool
|
||||
IsMultiLingual() bool
|
||||
IsMultilingual() bool
|
||||
NoBuildLock() bool
|
||||
BaseConfig() BaseConfig
|
||||
Dirs() CommonDirs
|
||||
Quiet() bool
|
||||
DirsBase() CommonDirs
|
||||
ContentTypes() ContentTypesProvider
|
||||
GetConfigSection(string) any
|
||||
GetConfig() any
|
||||
CanonifyURLs() bool
|
||||
@@ -75,6 +76,15 @@ type AllProvider interface {
|
||||
EnableEmoji() bool
|
||||
}
|
||||
|
||||
// We cannot import the media package as that would create a circular dependency.
|
||||
// This interface defineds a sub set of what media.ContentTypes provides.
|
||||
type ContentTypesProvider interface {
|
||||
IsContentSuffix(suffix string) bool
|
||||
IsContentFile(filename string) bool
|
||||
IsIndexContentFile(filename string) bool
|
||||
IsHTMLSuffix(suffix string) bool
|
||||
}
|
||||
|
||||
// Provider provides the configuration settings for Hugo.
|
||||
type Provider interface {
|
||||
GetString(key string) string
|
||||
|
||||
@@ -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,
|
||||
|
||||
+1
-3
@@ -29,8 +29,6 @@ import (
|
||||
"github.com/gohugoio/hugo/common/hstrings"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
@@ -98,7 +96,7 @@ func NewContent(h *hugolib.HugoSites, kind, targetPath string, force bool) error
|
||||
return "", fmt.Errorf("failed to resolve %q to an archetype template", targetPath)
|
||||
}
|
||||
|
||||
if !files.IsContentFile(b.targetPath) {
|
||||
if !h.Conf.ContentTypes().IsContentFile(b.targetPath) {
|
||||
return "", fmt.Errorf("target path %q is not a known content format", b.targetPath)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ or site.Language.LanguageCode site.Language.Lang }}" dir="{{ or site.Language.LanguageDirection `ltr` }}">
|
||||
<html lang="{{ or site.Language.LanguageCode }}" dir="{{ or site.Language.LanguageDirection `ltr` }}">
|
||||
<head>
|
||||
{{ partial "head.html" . }}
|
||||
</head>
|
||||
|
||||
@@ -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
+18
-32
@@ -15,11 +15,13 @@ import (
|
||||
"github.com/gohugoio/hugo/cache/filecache"
|
||||
"github.com/gohugoio/hugo/common/hexec"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/config/allconfig"
|
||||
"github.com/gohugoio/hugo/config/security"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
"github.com/gohugoio/hugo/resources/postpub"
|
||||
@@ -85,7 +87,7 @@ type Deps struct {
|
||||
BuildEndListeners *Listeners
|
||||
|
||||
// Resources that gets closed when the build is done or the server shuts down.
|
||||
BuildClosers *Closers
|
||||
BuildClosers *types.Closers
|
||||
|
||||
// This is common/global for all sites.
|
||||
BuildState *BuildState
|
||||
@@ -143,7 +145,7 @@ func (d *Deps) Init() error {
|
||||
}
|
||||
|
||||
if d.BuildClosers == nil {
|
||||
d.BuildClosers = &Closers{}
|
||||
d.BuildClosers = &types.Closers{}
|
||||
}
|
||||
|
||||
if d.Metrics == nil && d.Conf.TemplateMetrics() {
|
||||
@@ -155,7 +157,7 @@ func (d *Deps) Init() error {
|
||||
}
|
||||
|
||||
if d.MemCache == nil {
|
||||
d.MemCache = dynacache.New(dynacache.Options{Running: d.Conf.Running(), Log: d.Log})
|
||||
d.MemCache = dynacache.New(dynacache.Options{Watching: d.Conf.Watching(), Log: d.Log})
|
||||
}
|
||||
|
||||
if d.PathSpec == nil {
|
||||
@@ -208,7 +210,7 @@ func (d *Deps) Init() error {
|
||||
return fmt.Errorf("failed to create file caches from configuration: %w", err)
|
||||
}
|
||||
|
||||
resourceSpec, err := resources.NewSpec(d.PathSpec, common, fileCaches, d.MemCache, d.BuildState, d.Log, d, d.ExecHelper)
|
||||
resourceSpec, err := resources.NewSpec(d.PathSpec, common, fileCaches, d.MemCache, d.BuildState, d.Log, d, d.ExecHelper, d.BuildClosers, d.BuildState)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create resource spec: %w", err)
|
||||
}
|
||||
@@ -353,6 +355,9 @@ type DepsCfg struct {
|
||||
|
||||
// i18n handling.
|
||||
TranslationProvider ResourceProvider
|
||||
|
||||
// ChangesFromBuild for changes passed back to the server/watch process.
|
||||
ChangesFromBuild chan []identity.Identity
|
||||
}
|
||||
|
||||
// BuildState are state used during a build.
|
||||
@@ -361,11 +366,19 @@ type BuildState struct {
|
||||
|
||||
mu sync.Mutex // protects state below.
|
||||
|
||||
// A set of ilenames in /public that
|
||||
OnSignalRebuild func(ids ...identity.Identity)
|
||||
|
||||
// A set of filenames in /public that
|
||||
// contains a post-processing prefix.
|
||||
filenamesWithPostPrefix map[string]bool
|
||||
}
|
||||
|
||||
var _ identity.SignalRebuilder = (*BuildState)(nil)
|
||||
|
||||
func (b *BuildState) SignalRebuild(ids ...identity.Identity) {
|
||||
b.OnSignalRebuild(ids...)
|
||||
}
|
||||
|
||||
func (b *BuildState) AddFilenameWithPostPrefix(filename string) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
@@ -389,30 +402,3 @@ func (b *BuildState) GetFilenamesWithPostPrefix() []string {
|
||||
func (b *BuildState) Incr() int {
|
||||
return int(atomic.AddUint64(&b.counter, uint64(1)))
|
||||
}
|
||||
|
||||
type Closer interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
type Closers struct {
|
||||
mu sync.Mutex
|
||||
cs []Closer
|
||||
}
|
||||
|
||||
func (cs *Closers) Add(c Closer) {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
cs.cs = append(cs.cs, c)
|
||||
}
|
||||
|
||||
func (cs *Closers) Close() error {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
for _, c := range cs.cs {
|
||||
c.Close()
|
||||
}
|
||||
|
||||
cs.cs = cs.cs[:0]
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -20,10 +20,11 @@ hugo gen chromastyles [flags] [args]
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for chromastyles
|
||||
--highlightStyle string style used for highlighting lines (see https://github.com/alecthomas/chroma)
|
||||
--linesStyle string style used for line numbers (see https://github.com/alecthomas/chroma)
|
||||
--style string highlighter style (see https://xyproto.github.io/splash/docs/) (default "friendly")
|
||||
-h, --help help for chromastyles
|
||||
--highlightStyle string foreground and background colors for highlighted lines, e.g. --highlightStyle "#fff000 bg:#000fff"
|
||||
--lineNumbersInlineStyle string foreground and background colors for inline line numbers, e.g. --lineNumbersInlineStyle "#fff000 bg:#000fff"
|
||||
--lineNumbersTableStyle string foreground and background colors for table line numbers, e.g. --lineNumbersTableStyle "#fff000 bg:#000fff"
|
||||
--style string highlighter style (see https://xyproto.github.io/splash/docs/) (default "friendly")
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
@@ -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].
|
||||
|
||||
|
||||
+79
-6
@@ -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
|
||||
@@ -919,7 +939,6 @@ config:
|
||||
cacheBusters:
|
||||
- source: (postcss|tailwind)\.config\.js
|
||||
target: (css|styles|scss|sass)
|
||||
duplicateResourceFiles: false
|
||||
noJSConfigInAssets: false
|
||||
useResourceCacheWhen: fallback
|
||||
buildDrafts: false
|
||||
@@ -946,6 +965,7 @@ config:
|
||||
dir: :cacheDir/modules
|
||||
maxAge: -1
|
||||
canonifyURLs: false
|
||||
capitalizeListTitles: true
|
||||
cascade: []
|
||||
cleanDestinationDir: false
|
||||
contentDir: content
|
||||
@@ -1044,6 +1064,15 @@ config:
|
||||
enable: false
|
||||
escapedSpace: false
|
||||
definitionList: true
|
||||
extras:
|
||||
insert:
|
||||
enable: false
|
||||
mark:
|
||||
enable: false
|
||||
subscript:
|
||||
enable: false
|
||||
superscript:
|
||||
enable: false
|
||||
footnote: true
|
||||
linkify: true
|
||||
linkifyProtocol: https
|
||||
@@ -1179,6 +1208,12 @@ config:
|
||||
delimiter: .
|
||||
suffixes:
|
||||
- webp
|
||||
text/asciidoc:
|
||||
delimiter: .
|
||||
suffixes:
|
||||
- adoc
|
||||
- asciidoc
|
||||
- ad
|
||||
text/calendar:
|
||||
delimiter: .
|
||||
suffixes:
|
||||
@@ -1195,6 +1230,7 @@ config:
|
||||
delimiter: .
|
||||
suffixes:
|
||||
- html
|
||||
- htm
|
||||
text/javascript:
|
||||
delimiter: .
|
||||
suffixes:
|
||||
@@ -1209,11 +1245,25 @@ config:
|
||||
delimiter: .
|
||||
suffixes:
|
||||
- md
|
||||
- mdown
|
||||
- markdown
|
||||
text/org:
|
||||
delimiter: .
|
||||
suffixes:
|
||||
- org
|
||||
text/pandoc:
|
||||
delimiter: .
|
||||
suffixes:
|
||||
- pandoc
|
||||
- pdc
|
||||
text/plain:
|
||||
delimiter: .
|
||||
suffixes:
|
||||
- txt
|
||||
text/rst:
|
||||
delimiter: .
|
||||
suffixes:
|
||||
- rst
|
||||
text/tsx:
|
||||
delimiter: .
|
||||
suffixes:
|
||||
@@ -1536,10 +1586,8 @@ config:
|
||||
disqus:
|
||||
disable: false
|
||||
googleAnalytics:
|
||||
anonymizeIP: false
|
||||
disable: false
|
||||
respectDoNotTrack: false
|
||||
useSessionStorage: false
|
||||
instagram:
|
||||
disable: false
|
||||
simple: false
|
||||
@@ -1585,6 +1633,7 @@ config:
|
||||
toLower: false
|
||||
relativeURLs: false
|
||||
removePathAccents: false
|
||||
renderSegments: null
|
||||
resourceDir: resources
|
||||
sectionPagesMenu: ""
|
||||
security:
|
||||
@@ -1607,6 +1656,7 @@ config:
|
||||
- (?i)GET|POST
|
||||
urls:
|
||||
- .*
|
||||
segments: {}
|
||||
server:
|
||||
headers: null
|
||||
redirects:
|
||||
@@ -1628,6 +1678,7 @@ config:
|
||||
disableInlineCSS: false
|
||||
sitemap:
|
||||
changeFreq: ""
|
||||
disable: false
|
||||
filename: sitemap.xml
|
||||
priority: -1
|
||||
social: null
|
||||
@@ -1704,6 +1755,8 @@ config_helpers:
|
||||
_merge: none
|
||||
security:
|
||||
_merge: none
|
||||
segments:
|
||||
_merge: none
|
||||
server:
|
||||
_merge: none
|
||||
services:
|
||||
@@ -2774,8 +2827,8 @@ tpl:
|
||||
{{ $m.Set "Hugo" "Rocks!" }}
|
||||
{{ $m.Values | debug.Dump | safeHTML }}
|
||||
- |-
|
||||
map[string]interface {}{
|
||||
"Hugo": "Rocks!",
|
||||
{
|
||||
"Hugo": "Rocks!"
|
||||
}
|
||||
TestDeprecationErr:
|
||||
Aliases: null
|
||||
@@ -2965,6 +3018,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
|
||||
@@ -3978,6 +4046,11 @@ tpl:
|
||||
- s
|
||||
Description: CountWords returns the approximate word count in s.
|
||||
Examples: []
|
||||
Diff:
|
||||
Aliases: null
|
||||
Args: null
|
||||
Description: ""
|
||||
Examples: null
|
||||
FindRE:
|
||||
Aliases:
|
||||
- findRE
|
||||
@@ -4542,5 +4615,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: []
|
||||
|
||||
@@ -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,14 +18,14 @@ 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.1
|
||||
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
|
||||
@@ -35,7 +35,9 @@ require (
|
||||
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/httpcache v0.7.0
|
||||
github.com/gohugoio/hugo-goldmark-extensions/extras v0.1.0
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.2.0
|
||||
github.com/gohugoio/locales v0.14.0
|
||||
github.com/gohugoio/localescompressed v1.0.1
|
||||
github.com/gohugoio/testmodBuilder/mods v0.0.0-20190520184928-c56af20f2e95
|
||||
@@ -55,28 +57,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.2
|
||||
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.17
|
||||
github.com/tdewolff/parse/v2 v2.7.12
|
||||
github.com/yuin/goldmark v1.7.0
|
||||
github.com/tdewolff/minify/v2 v2.20.20
|
||||
github.com/tdewolff/parse/v2 v2.7.13
|
||||
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.21.0
|
||||
golang.org/x/sync v0.6.0
|
||||
golang.org/x/text v0.14.0
|
||||
golang.org/x/tools v0.18.0
|
||||
golang.org/x/image v0.16.0
|
||||
golang.org/x/mod v0.17.0
|
||||
golang.org/x/net v0.25.0
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/text v0.15.0
|
||||
golang.org/x/tools v0.20.0
|
||||
google.golang.org/api v0.152.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
@@ -100,8 +102,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
|
||||
@@ -112,9 +114,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
|
||||
@@ -142,9 +144,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.19.0 // indirect
|
||||
golang.org/x/crypto v0.23.0 // indirect
|
||||
golang.org/x/oauth2 v0.15.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
golang.org/x/sys v0.20.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
|
||||
@@ -152,7 +154,7 @@ require (
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
|
||||
google.golang.org/grpc v1.59.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
software.sslmate.com/src/go-pkcs12 v0.2.0 // indirect
|
||||
|
||||
@@ -53,6 +53,7 @@ github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0/go.mod h1:1fXstnBMas5kzG
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0 h1:d81/ng9rET2YqdVkVwkb6EXeRrLJIwyGnJcAlAWKwhs=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0 h1:Ma67P/GGprNwsslzEH6+Kb8nybI8jpDTm4Wmzu2ReK8=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.2.0/go.mod h1:c+Lifp3EDEamAkPVzMooRNOK6CZjNSdEnf1A7jsI9u4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0 h1:gggzg0SUMs6SQbEw+3LoSsYf9YMjkupeAnHMX8O9mmY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0/go.mod h1:+6KLcKIVgxoBDMqMO/Nvy7bZ9a0nbU3I1DtFQK3YvB4=
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
|
||||
@@ -66,16 +67,18 @@ 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/assert/v2 v2.6.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
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/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
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 +89,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 +115,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 +141,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,9 +171,10 @@ 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/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
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=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
@@ -179,8 +183,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.1 h1:ueyMIL19umCcJTSxiBH/QmPipgGt8hEDM24pdfowgEc=
|
||||
github.com/evanw/esbuild v0.20.1/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=
|
||||
@@ -205,14 +209,23 @@ github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1Rf
|
||||
github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw=
|
||||
github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA=
|
||||
github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs=
|
||||
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/httpcache v0.5.0 h1:9xi4VuXd+KT3h0jOs8DlZxTMu5CtjDr0BvQMAuL/O5I=
|
||||
github.com/gohugoio/httpcache v0.5.0/go.mod h1:fMlPrdY/vVJhAriLZnrF5QpN3BNAcoBClgAyQd+lGFI=
|
||||
github.com/gohugoio/httpcache v0.6.0 h1:5pYJM43Yoc4uvIJ+/e770PS48srTumvuQZpuBfGFZV0=
|
||||
github.com/gohugoio/httpcache v0.6.0/go.mod h1:fMlPrdY/vVJhAriLZnrF5QpN3BNAcoBClgAyQd+lGFI=
|
||||
github.com/gohugoio/httpcache v0.7.0 h1:ukPnn04Rgvx48JIinZvZetBfHaWE7I01JR2Q2RrQ3Vs=
|
||||
github.com/gohugoio/httpcache v0.7.0/go.mod h1:fMlPrdY/vVJhAriLZnrF5QpN3BNAcoBClgAyQd+lGFI=
|
||||
github.com/gohugoio/hugo-goldmark-extensions/extras v0.1.0 h1:YhxZNU8y2vxV6Ibr7QJzzUlpr8oHHWX/l+Q1R/a5Zao=
|
||||
github.com/gohugoio/hugo-goldmark-extensions/extras v0.1.0/go.mod h1:0cuvOnGKW7WeXA3i7qK6IS07FH1bgJ2XzOjQ7BMJYH4=
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.2.0 h1:PCtO5l++psZf48yen2LxQ3JiOXxaRC6v0594NeHvGZg=
|
||||
github.com/gohugoio/hugo-goldmark-extensions/passthrough v0.2.0/go.mod h1:g9CCh+Ci2IMbPUrVJuXbBTrA+rIIx5+hDQ4EXYaQDoM=
|
||||
github.com/gohugoio/locales v0.14.0 h1:Q0gpsZwfv7ATHMbcTNepFd59H7GoykzWJIxi113XGDc=
|
||||
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=
|
||||
@@ -270,12 +283,15 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-replayers/grpcreplay v1.1.0 h1:S5+I3zYyZ+GQz68OfbURDdt/+cSMqCK1wrvNx7WBzTE=
|
||||
github.com/google/go-replayers/grpcreplay v1.1.0/go.mod h1:qzAvJ8/wi57zq7gWqaE6AwLM6miiXUQwP1S+I9icmhk=
|
||||
github.com/google/go-replayers/httpreplay v1.2.0 h1:VM1wEyyjaoU53BwrOnaf9VhAyQQEEioJvFYxYcLRKzk=
|
||||
github.com/google/go-replayers/httpreplay v1.2.0/go.mod h1:WahEFFZZ7a1P4VM1qEeHy+tME4bwyqPcwWbNlUI1Mcg=
|
||||
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
|
||||
github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
@@ -311,6 +327,7 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
@@ -376,8 +393,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.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/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=
|
||||
@@ -390,6 +407,7 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
@@ -410,13 +428,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=
|
||||
@@ -425,15 +444,18 @@ 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.17 h1:zGqEDhspr3XjSrQI/56vw9IdAhLAaKTLXWnDBsxNVt8=
|
||||
github.com/tdewolff/minify/v2 v2.20.17/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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tdewolff/minify/v2 v2.20.20 h1:vhULb+VsW2twkplgsawAoUY957efb+EdiZ7zu5fUhhk=
|
||||
github.com/tdewolff/minify/v2 v2.20.20/go.mod h1:GYaLXFpIIwsX99apQHXfGdISUdlA98wmaoWxjT9C37k=
|
||||
github.com/tdewolff/parse/v2 v2.7.13 h1:iSiwOUkCYLNfapHoqdLcqZVgvQ0jrsao8YYKP/UJYTI=
|
||||
github.com/tdewolff/parse/v2 v2.7.13/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
|
||||
github.com/tdewolff/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/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@@ -441,8 +463,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=
|
||||
@@ -466,8 +488,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.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/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=
|
||||
@@ -483,8 +505,8 @@ golang.org/x/exp v0.0.0-20221031165847-c99f073a8326/go.mod h1:CxIveKay+FTh1D0yPZ
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw=
|
||||
golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -508,8 +530,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=
|
||||
@@ -545,8 +567,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.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/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=
|
||||
@@ -570,8 +592,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=
|
||||
@@ -615,8 +637,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.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/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=
|
||||
@@ -628,8 +650,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -685,8 +707,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.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ=
|
||||
golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg=
|
||||
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=
|
||||
@@ -795,11 +817,12 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/neurosnap/sentences.v1 v1.0.6/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3JmTb5LSxFRQo0=
|
||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
||||
|
||||
+25
-26
@@ -26,6 +26,7 @@ import (
|
||||
|
||||
"github.com/gohugoio/hugo/common/hexec"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
|
||||
@@ -36,11 +37,6 @@ import (
|
||||
"github.com/gohugoio/hugo/config"
|
||||
)
|
||||
|
||||
var (
|
||||
openingPTag = []byte("<p>")
|
||||
closingPTag = []byte("</p>")
|
||||
)
|
||||
|
||||
// ContentSpec provides functionality to render markdown content.
|
||||
type ContentSpec struct {
|
||||
Converters markup.ConverterProvider
|
||||
@@ -140,20 +136,16 @@ func (c *ContentSpec) SanitizeAnchorName(s string) string {
|
||||
}
|
||||
|
||||
func (c *ContentSpec) ResolveMarkup(in string) string {
|
||||
if c == nil {
|
||||
panic("nil ContentSpec")
|
||||
}
|
||||
in = strings.ToLower(in)
|
||||
switch in {
|
||||
case "md", "markdown", "mdown":
|
||||
return "markdown"
|
||||
case "html", "htm":
|
||||
return "html"
|
||||
default:
|
||||
if conv := c.Converters.Get(in); conv != nil {
|
||||
return conv.Name()
|
||||
}
|
||||
|
||||
if mediaType, found := c.Cfg.ContentTypes().(media.ContentTypes).Types().GetBestMatch(markup.ResolveMarkup(in)); found {
|
||||
return mediaType.SubType
|
||||
}
|
||||
|
||||
if conv := c.Converters.Get(in); conv != nil {
|
||||
return markup.ResolveMarkup(conv.Name())
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -242,19 +234,26 @@ func (c *ContentSpec) TruncateWordsToWholeSentence(s string) (string, bool) {
|
||||
return strings.TrimSpace(s[:endIndex]), endIndex < len(s)
|
||||
}
|
||||
|
||||
// TrimShortHTML removes the <p>/</p> tags from HTML input in the situation
|
||||
// where said tags are the only <p> tags in the input and enclose the content
|
||||
// of the input (whitespace excluded).
|
||||
func (c *ContentSpec) TrimShortHTML(input []byte) []byte {
|
||||
if bytes.Count(input, openingPTag) == 1 {
|
||||
// TrimShortHTML removes the outer tags from HTML input where (a) the opening
|
||||
// tag is present only once with the input, and (b) the opening and closing
|
||||
// tags wrap the input after white space removal.
|
||||
func (c *ContentSpec) TrimShortHTML(input []byte, markup string) []byte {
|
||||
openingTag := []byte("<p>")
|
||||
closingTag := []byte("</p>")
|
||||
|
||||
if markup == media.DefaultContentTypes.AsciiDoc.SubType {
|
||||
openingTag = []byte("<div class=\"paragraph\">\n<p>")
|
||||
closingTag = []byte("</p>\n</div>")
|
||||
}
|
||||
|
||||
if bytes.Count(input, openingTag) == 1 {
|
||||
input = bytes.TrimSpace(input)
|
||||
if bytes.HasPrefix(input, openingPTag) && bytes.HasSuffix(input, closingPTag) {
|
||||
input = bytes.TrimPrefix(input, openingPTag)
|
||||
input = bytes.TrimSuffix(input, closingPTag)
|
||||
if bytes.HasPrefix(input, openingTag) && bytes.HasSuffix(input, closingTag) {
|
||||
input = bytes.TrimPrefix(input, openingTag)
|
||||
input = bytes.TrimSuffix(input, closingTag)
|
||||
input = bytes.TrimSpace(input)
|
||||
}
|
||||
}
|
||||
|
||||
return input
|
||||
}
|
||||
|
||||
|
||||
+24
-13
@@ -26,30 +26,41 @@ import (
|
||||
|
||||
func TestTrimShortHTML(t *testing.T) {
|
||||
tests := []struct {
|
||||
input, output []byte
|
||||
markup string
|
||||
input []byte
|
||||
output []byte
|
||||
}{
|
||||
{[]byte(""), []byte("")},
|
||||
{[]byte("Plain text"), []byte("Plain text")},
|
||||
// This seems wrong. Why touch it if it doesn't have p tag?
|
||||
// {[]byte(" \t\n Whitespace text\n\n"), []byte("Whitespace text")},
|
||||
{[]byte("<p>Simple paragraph</p>"), []byte("Simple paragraph")},
|
||||
{[]byte("\n \n \t <p> \t Whitespace\nHTML \n\t </p>\n\t"), []byte("Whitespace\nHTML")},
|
||||
{[]byte("<p>Multiple</p><p>paragraphs</p>"), []byte("<p>Multiple</p><p>paragraphs</p>")},
|
||||
{[]byte("<p>Nested<p>paragraphs</p></p>"), []byte("<p>Nested<p>paragraphs</p></p>")},
|
||||
{[]byte("<p>Hello</p>\n<ul>\n<li>list1</li>\n<li>list2</li>\n</ul>"), []byte("<p>Hello</p>\n<ul>\n<li>list1</li>\n<li>list2</li>\n</ul>")},
|
||||
// Issue #11698
|
||||
{[]byte("<h2 id=`a`>b</h2>\n\n<p>c</p>"), []byte("<h2 id=`a`>b</h2>\n\n<p>c</p>")},
|
||||
{"markdown", []byte(""), []byte("")},
|
||||
{"markdown", []byte("Plain text"), []byte("Plain text")},
|
||||
{"markdown", []byte("<p>Simple paragraph</p>"), []byte("Simple paragraph")},
|
||||
{"markdown", []byte("\n \n \t <p> \t Whitespace\nHTML \n\t </p>\n\t"), []byte("Whitespace\nHTML")},
|
||||
{"markdown", []byte("<p>Multiple</p><p>paragraphs</p>"), []byte("<p>Multiple</p><p>paragraphs</p>")},
|
||||
{"markdown", []byte("<p>Nested<p>paragraphs</p></p>"), []byte("<p>Nested<p>paragraphs</p></p>")},
|
||||
{"markdown", []byte("<p>Hello</p>\n<ul>\n<li>list1</li>\n<li>list2</li>\n</ul>"), []byte("<p>Hello</p>\n<ul>\n<li>list1</li>\n<li>list2</li>\n</ul>")},
|
||||
// Issue 11698
|
||||
{"markdown", []byte("<h2 id=`a`>b</h2>\n\n<p>c</p>"), []byte("<h2 id=`a`>b</h2>\n\n<p>c</p>")},
|
||||
// Issue 12369
|
||||
{"markdown", []byte("<div class=\"paragraph\">\n<p>foo</p>\n</div>"), []byte("<div class=\"paragraph\">\n<p>foo</p>\n</div>")},
|
||||
{"asciidoc", []byte("<div class=\"paragraph\">\n<p>foo</p>\n</div>"), []byte("foo")},
|
||||
}
|
||||
|
||||
c := newTestContentSpec(nil)
|
||||
for i, test := range tests {
|
||||
output := c.TrimShortHTML(test.input)
|
||||
output := c.TrimShortHTML(test.input, test.markup)
|
||||
if !bytes.Equal(test.output, output) {
|
||||
t.Errorf("Test %d failed. Expected %q got %q", i, test.output, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTrimShortHTML(b *testing.B) {
|
||||
c := newTestContentSpec(nil)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
c.TrimShortHTML([]byte("<p>Simple paragraph</p>"), "markdown")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBytesToHTML(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
c.Assert(helpers.BytesToHTML([]byte("dobedobedo")), qt.Equals, template.HTML("dobedobedo"))
|
||||
|
||||
+8
-1
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -35,9 +35,9 @@ func TestResolveMarkup(t *testing.T) {
|
||||
{"md", "markdown"},
|
||||
{"markdown", "markdown"},
|
||||
{"mdown", "markdown"},
|
||||
{"asciidocext", "asciidocext"},
|
||||
{"adoc", "asciidocext"},
|
||||
{"ad", "asciidocext"},
|
||||
{"asciidocext", "asciidoc"},
|
||||
{"adoc", "asciidoc"},
|
||||
{"ad", "asciidoc"},
|
||||
{"rst", "rst"},
|
||||
{"pandoc", "pandoc"},
|
||||
{"pdc", "pandoc"},
|
||||
|
||||
+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/"
|
||||
|
||||
@@ -16,6 +16,7 @@ package hqt
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
@@ -38,6 +39,11 @@ var IsSameType qt.Checker = &typeChecker{
|
||||
argNames: []string{"got", "want"},
|
||||
}
|
||||
|
||||
// IsSameFloat64 asserts that two float64 values are equal within a small delta.
|
||||
var IsSameFloat64 = qt.CmpEquals(cmp.Comparer(func(a, b float64) bool {
|
||||
return math.Abs(a-b) < 0.0001
|
||||
}))
|
||||
|
||||
type argNames []string
|
||||
|
||||
func (a argNames) ArgNames() []string {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -29,57 +29,13 @@ const (
|
||||
FilenameHugoStatsJSON = "hugo_stats.json"
|
||||
)
|
||||
|
||||
var (
|
||||
// This should be the only list of valid extensions for content files.
|
||||
contentFileExtensions = []string{
|
||||
"html", "htm",
|
||||
"mdown", "markdown", "md",
|
||||
"asciidoc", "adoc", "ad",
|
||||
"rest", "rst",
|
||||
"org",
|
||||
"pandoc", "pdc",
|
||||
}
|
||||
|
||||
contentFileExtensionsSet map[string]bool
|
||||
|
||||
htmlFileExtensions = []string{
|
||||
"html", "htm",
|
||||
}
|
||||
|
||||
htmlFileExtensionsSet map[string]bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
contentFileExtensionsSet = make(map[string]bool)
|
||||
for _, ext := range contentFileExtensions {
|
||||
contentFileExtensionsSet[ext] = true
|
||||
}
|
||||
htmlFileExtensionsSet = make(map[string]bool)
|
||||
for _, ext := range htmlFileExtensions {
|
||||
htmlFileExtensionsSet[ext] = true
|
||||
}
|
||||
func IsGoTmplExt(ext string) bool {
|
||||
return ext == "gotmpl"
|
||||
}
|
||||
|
||||
func IsContentFile(filename string) bool {
|
||||
return contentFileExtensionsSet[strings.TrimPrefix(filepath.Ext(filename), ".")]
|
||||
}
|
||||
|
||||
func IsIndexContentFile(filename string) bool {
|
||||
if !IsContentFile(filename) {
|
||||
return false
|
||||
}
|
||||
|
||||
base := filepath.Base(filename)
|
||||
|
||||
return strings.HasPrefix(base, "index.") || strings.HasPrefix(base, "_index.")
|
||||
}
|
||||
|
||||
func IsHTML(ext string) bool {
|
||||
return htmlFileExtensionsSet[ext]
|
||||
}
|
||||
|
||||
func IsContentExt(ext string) bool {
|
||||
return contentFileExtensionsSet[ext]
|
||||
// Supported data file extensions for _content.* files.
|
||||
func IsContentDataExt(ext string) bool {
|
||||
return IsGoTmplExt(ext)
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -93,6 +49,8 @@ const (
|
||||
|
||||
FolderResources = "resources"
|
||||
FolderJSConfig = "_jsconfig" // Mounted below /assets with postcss.config.js etc.
|
||||
|
||||
NameContentData = "_content"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -14,22 +14,11 @@
|
||||
package files
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func TestIsContentFile(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(IsContentFile(filepath.FromSlash("my/file.md")), qt.Equals, true)
|
||||
c.Assert(IsContentFile(filepath.FromSlash("my/file.ad")), qt.Equals, true)
|
||||
c.Assert(IsContentFile(filepath.FromSlash("textfile.txt")), qt.Equals, false)
|
||||
c.Assert(IsContentExt("md"), qt.Equals, true)
|
||||
c.Assert(IsContentExt("json"), qt.Equals, false)
|
||||
}
|
||||
|
||||
func TestComponentFolders(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
|
||||
+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"},
|
||||
|
||||
+8
-2
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/gohugoio/hugo/common/herrors"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
@@ -50,7 +51,8 @@ type WalkwayConfig struct {
|
||||
Root string
|
||||
|
||||
// The logger to use.
|
||||
Logger loggers.Logger
|
||||
Logger loggers.Logger
|
||||
PathParser *paths.PathParser
|
||||
|
||||
// One or both of these may be pre-set.
|
||||
Info FileMetaInfo // The start info.
|
||||
@@ -72,6 +74,10 @@ func NewWalkway(cfg WalkwayConfig) *Walkway {
|
||||
panic("fs must be set")
|
||||
}
|
||||
|
||||
if cfg.PathParser == nil {
|
||||
cfg.PathParser = media.DefaultPathParser
|
||||
}
|
||||
|
||||
logger := cfg.Logger
|
||||
if logger == nil {
|
||||
logger = loggers.NewDefault()
|
||||
@@ -161,7 +167,7 @@ func (w *Walkway) walk(path string, info FileMetaInfo, dirEntries []FileMetaInfo
|
||||
dirEntries = DirEntriesToFileMetaInfos(fis)
|
||||
for _, fi := range dirEntries {
|
||||
if fi.Meta().PathInfo == nil {
|
||||
fi.Meta().PathInfo = paths.Parse("", filepath.Join(pathRel, fi.Name()))
|
||||
fi.Meta().PathInfo = w.cfg.PathParser.Parse("", filepath.Join(pathRel, fi.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+142
-1
@@ -329,7 +329,7 @@ cascade:
|
||||
|
||||
counters := &buildCounters{}
|
||||
b.Build(BuildCfg{testCounters: counters})
|
||||
b.Assert(int(counters.contentRenderCounter.Load()), qt.Equals, 2)
|
||||
b.Assert(int(counters.contentRenderCounter.Load()), qt.Equals, 1)
|
||||
|
||||
b.AssertFileContent("public/post/index.html", `Banner: post.jpg|Layout: postlayout|Type: posttype|Content: <p>content edit</p>`)
|
||||
b.AssertFileContent("public/post/dir/p1/index.html", `Banner: post.jpg|Layout: postlayout|`)
|
||||
@@ -672,8 +672,59 @@ S1|p1:|p2:p2|
|
||||
})
|
||||
}
|
||||
|
||||
func TestCascadeEditIssue12449(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.com"
|
||||
disableKinds = ['sitemap','rss', 'home', 'taxonomy','term']
|
||||
disableLiveReload = true
|
||||
-- layouts/_default/list.html --
|
||||
Title: {{ .Title }}|{{ .Content }}|cascadeparam: {{ .Params.cascadeparam }}|
|
||||
-- layouts/_default/single.html --
|
||||
Title: {{ .Title }}|{{ .Content }}|cascadeparam: {{ .Params.cascadeparam }}|
|
||||
-- content/mysect/_index.md --
|
||||
---
|
||||
title: mysect
|
||||
cascade:
|
||||
description: descriptionvalue
|
||||
params:
|
||||
cascadeparam: cascadeparamvalue
|
||||
---
|
||||
mysect-content|
|
||||
-- content/mysect/p1/index.md --
|
||||
---
|
||||
slug: p1
|
||||
---
|
||||
p1-content|
|
||||
-- content/mysect/subsect/_index.md --
|
||||
---
|
||||
slug: subsect
|
||||
---
|
||||
subsect-content|
|
||||
`
|
||||
|
||||
b := TestRunning(t, files)
|
||||
|
||||
// Make the cascade set the title.
|
||||
b.EditFileReplaceAll("content/mysect/_index.md", "description: descriptionvalue", "title: cascadetitle").Build()
|
||||
b.AssertFileContent("public/mysect/subsect/index.html", "Title: cascadetitle|")
|
||||
|
||||
// Edit cascade title.
|
||||
b.EditFileReplaceAll("content/mysect/_index.md", "title: cascadetitle", "title: cascadetitle-edit").Build()
|
||||
b.AssertFileContent("public/mysect/subsect/index.html", "Title: cascadetitle-edit|")
|
||||
|
||||
// Revert title change.
|
||||
// The step below failed in #12449.
|
||||
b.EditFileReplaceAll("content/mysect/_index.md", "title: cascadetitle-edit", "description: descriptionvalue").Build()
|
||||
b.AssertFileContent("public/mysect/subsect/index.html", "Title: |")
|
||||
}
|
||||
|
||||
// Issue 11977.
|
||||
func TestCascadeExtensionInPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = "https://example.org"
|
||||
@@ -700,3 +751,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)
|
||||
}
|
||||
|
||||
+44
-25
@@ -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)
|
||||
|
||||
@@ -1144,7 +1144,7 @@ Home.
|
||||
|
||||
enConfig := b.H.Sites[0].conf
|
||||
m, _ := enConfig.MediaTypes.Config.GetByType("text/html")
|
||||
b.Assert(m.Suffixes(), qt.DeepEquals, []string{"html"})
|
||||
b.Assert(m.Suffixes(), qt.DeepEquals, []string{"html", "htm"})
|
||||
|
||||
svConfig := b.H.Sites[1].conf
|
||||
f, _ := svConfig.OutputFormats.Config.GetByName("html")
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+244
-19
@@ -14,6 +14,7 @@
|
||||
package hugolib
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@@ -23,10 +24,13 @@ import (
|
||||
"github.com/bep/logg"
|
||||
"github.com/gohugoio/hugo/common/hugio"
|
||||
"github.com/gohugoio/hugo/common/paths"
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
"github.com/gohugoio/hugo/hugolib/pagesfromdata"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/source"
|
||||
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
"github.com/gohugoio/hugo/resources/page/pagemeta"
|
||||
"github.com/gohugoio/hugo/resources/resource"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
@@ -51,9 +55,11 @@ type contentMapConfig struct {
|
||||
var _ contentNodeI = (*resourceSource)(nil)
|
||||
|
||||
type resourceSource struct {
|
||||
path *paths.Path
|
||||
opener hugio.OpenReadSeekCloser
|
||||
fi hugofs.FileMetaInfo
|
||||
langIndex int
|
||||
path *paths.Path
|
||||
opener hugio.OpenReadSeekCloser
|
||||
fi hugofs.FileMetaInfo
|
||||
rc *pagemeta.ResourceConfig
|
||||
|
||||
r resource.Resource
|
||||
}
|
||||
@@ -64,11 +70,7 @@ func (r resourceSource) clone() *resourceSource {
|
||||
}
|
||||
|
||||
func (r *resourceSource) LangIndex() int {
|
||||
if r.r != nil && r.isPage() {
|
||||
return r.r.(*pageState).s.languagei
|
||||
}
|
||||
|
||||
return r.fi.Meta().LangIndex
|
||||
return r.langIndex
|
||||
}
|
||||
|
||||
func (r *resourceSource) MarkStale() {
|
||||
@@ -162,12 +164,62 @@ func (cfg contentMapConfig) getTaxonomyConfig(s string) (v viewName) {
|
||||
return
|
||||
}
|
||||
|
||||
func (m *pageMap) AddFi(fi hugofs.FileMetaInfo) error {
|
||||
func (m *pageMap) insertPageWithLock(s string, p *pageState) (contentNodeI, contentNodeI, bool) {
|
||||
u, n, replaced := m.treePages.InsertIntoValuesDimensionWithLock(s, p)
|
||||
|
||||
if replaced && !m.s.h.isRebuild() && m.s.conf.PrintPathWarnings {
|
||||
var messageDetail string
|
||||
if p1, ok := n.(*pageState); ok && p1.File() != nil {
|
||||
messageDetail = fmt.Sprintf(" file: %q", p1.File().Filename())
|
||||
}
|
||||
if p2, ok := u.(*pageState); ok && p2.File() != nil {
|
||||
messageDetail += fmt.Sprintf(" file: %q", p2.File().Filename())
|
||||
}
|
||||
|
||||
m.s.Log.Warnf("Duplicate content path: %q%s", s, messageDetail)
|
||||
}
|
||||
|
||||
return u, n, replaced
|
||||
}
|
||||
|
||||
func (m *pageMap) insertResourceWithLock(s string, r contentNodeI) (contentNodeI, contentNodeI, bool) {
|
||||
u, n, replaced := m.treeResources.InsertIntoValuesDimensionWithLock(s, r)
|
||||
if replaced {
|
||||
m.handleDuplicateResourcePath(s, r, n)
|
||||
}
|
||||
return u, n, replaced
|
||||
}
|
||||
|
||||
func (m *pageMap) insertResource(s string, r contentNodeI) (contentNodeI, contentNodeI, bool) {
|
||||
u, n, replaced := m.treeResources.InsertIntoValuesDimension(s, r)
|
||||
if replaced {
|
||||
m.handleDuplicateResourcePath(s, r, n)
|
||||
}
|
||||
return u, n, replaced
|
||||
}
|
||||
|
||||
func (m *pageMap) handleDuplicateResourcePath(s string, updated, existing contentNodeI) {
|
||||
if m.s.h.isRebuild() || !m.s.conf.PrintPathWarnings {
|
||||
return
|
||||
}
|
||||
var messageDetail string
|
||||
if r1, ok := existing.(*resourceSource); ok && r1.fi != nil {
|
||||
messageDetail = fmt.Sprintf(" file: %q", r1.fi.Meta().Filename)
|
||||
}
|
||||
if r2, ok := updated.(*resourceSource); ok && r2.fi != nil {
|
||||
messageDetail += fmt.Sprintf(" file: %q", r2.fi.Meta().Filename)
|
||||
}
|
||||
|
||||
m.s.Log.Warnf("Duplicate resource path: %q%s", s, messageDetail)
|
||||
}
|
||||
|
||||
func (m *pageMap) AddFi(fi hugofs.FileMetaInfo, buildConfig *BuildCfg) (pageCount uint64, resourceCount uint64, addErr error) {
|
||||
if fi.IsDir() {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
insertResource := func(fim hugofs.FileMetaInfo) error {
|
||||
resourceCount++
|
||||
pi := fi.Meta().PathInfo
|
||||
key := pi.Base()
|
||||
tree := m.treeResources
|
||||
@@ -181,7 +233,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{
|
||||
@@ -193,14 +245,18 @@ 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}
|
||||
rs = &resourceSource{r: pageResource, langIndex: pageResource.s.languagei}
|
||||
} else {
|
||||
rs = &resourceSource{path: pi, opener: r, fi: fim}
|
||||
rs = &resourceSource{path: pi, opener: r, fi: fim, langIndex: fim.Meta().LangIndex}
|
||||
}
|
||||
|
||||
tree.InsertIntoValuesDimension(key, rs)
|
||||
_, _, _ = m.insertResource(key, rs)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -216,14 +272,27 @@ func (m *pageMap) AddFi(fi hugofs.FileMetaInfo) error {
|
||||
},
|
||||
))
|
||||
if err := insertResource(fi); err != nil {
|
||||
return err
|
||||
addErr = err
|
||||
return
|
||||
}
|
||||
case paths.PathTypeContentData:
|
||||
pc, rc, err := m.addPagesFromGoTmplFi(fi, buildConfig)
|
||||
pageCount += pc
|
||||
resourceCount += rc
|
||||
if err != nil {
|
||||
addErr = err
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
m.s.Log.Trace(logg.StringFunc(
|
||||
func() string {
|
||||
return fmt.Sprintf("insert bundle: %q", fi.Meta().Filename)
|
||||
},
|
||||
))
|
||||
|
||||
pageCount++
|
||||
|
||||
// A content file.
|
||||
p, pi, err := m.s.h.newPage(
|
||||
&pageMeta{
|
||||
@@ -233,17 +302,173 @@ func (m *pageMap) AddFi(fi hugofs.FileMetaInfo) error {
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
addErr = err
|
||||
return
|
||||
}
|
||||
if p == nil {
|
||||
// Disabled page.
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
m.treePages.InsertWithLock(pi.Base(), p)
|
||||
m.insertPageWithLock(pi.Base(), p)
|
||||
|
||||
}
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
func (m *pageMap) addPagesFromGoTmplFi(fi hugofs.FileMetaInfo, buildConfig *BuildCfg) (pageCount uint64, resourceCount uint64, addErr error) {
|
||||
meta := fi.Meta()
|
||||
pi := meta.PathInfo
|
||||
|
||||
m.s.Log.Trace(logg.StringFunc(
|
||||
func() string {
|
||||
return fmt.Sprintf("insert pages from data file: %q", fi.Meta().Filename)
|
||||
},
|
||||
))
|
||||
|
||||
if !files.IsGoTmplExt(pi.Ext()) {
|
||||
addErr = fmt.Errorf("unsupported data file extension %q", pi.Ext())
|
||||
return
|
||||
}
|
||||
|
||||
s := m.s.h.resolveSite(fi.Meta().Lang)
|
||||
f := source.NewFileInfo(fi)
|
||||
h := s.h
|
||||
|
||||
contentAdapter := s.pageMap.treePagesFromTemplateAdapters.Get(pi.Base())
|
||||
var rebuild bool
|
||||
if contentAdapter != nil {
|
||||
// Rebuild
|
||||
contentAdapter = contentAdapter.CloneForGoTmpl(fi)
|
||||
rebuild = true
|
||||
} else {
|
||||
contentAdapter = pagesfromdata.NewPagesFromTemplate(
|
||||
pagesfromdata.PagesFromTemplateOptions{
|
||||
GoTmplFi: fi,
|
||||
Site: s,
|
||||
DepsFromSite: func(s page.Site) pagesfromdata.PagesFromTemplateDeps {
|
||||
ss := s.(*Site)
|
||||
return pagesfromdata.PagesFromTemplateDeps{
|
||||
TmplFinder: ss.TextTmpl(),
|
||||
TmplExec: ss.Tmpl(),
|
||||
}
|
||||
},
|
||||
DependencyManager: s.Conf.NewIdentityManager("pagesfromdata"),
|
||||
Watching: s.Conf.Watching(),
|
||||
HandlePage: func(pt *pagesfromdata.PagesFromTemplate, pc *pagemeta.PageConfig) error {
|
||||
s := pt.Site.(*Site)
|
||||
if err := pc.Compile(pt.GoTmplFi.Meta().PathInfo.Base(), true, "", s.Log, s.conf.MediaTypes.Config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ps, pi, err := h.newPage(
|
||||
&pageMeta{
|
||||
f: f,
|
||||
s: s,
|
||||
pageMetaParams: &pageMetaParams{
|
||||
pageConfig: pc,
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ps == nil {
|
||||
// Disabled page.
|
||||
return nil
|
||||
}
|
||||
|
||||
u, n, replaced := s.pageMap.insertPageWithLock(pi.Base(), ps)
|
||||
|
||||
if h.isRebuild() {
|
||||
if replaced {
|
||||
pt.AddChange(n.GetIdentity())
|
||||
} else {
|
||||
pt.AddChange(u.GetIdentity())
|
||||
// New content not in use anywhere.
|
||||
// To make sure that these gets listed in any site.RegularPages ranges or similar
|
||||
// we could invalidate everything, but first try to collect a sample set
|
||||
// from the surrounding pages.
|
||||
var surroundingIDs []identity.Identity
|
||||
ids := h.pageTrees.collectIdentitiesSurrounding(pi.Base(), 10)
|
||||
if len(ids) > 0 {
|
||||
surroundingIDs = append(surroundingIDs, ids...)
|
||||
} else {
|
||||
// No surrounding pages found, so invalidate everything.
|
||||
surroundingIDs = []identity.Identity{identity.GenghisKhan}
|
||||
}
|
||||
for _, id := range surroundingIDs {
|
||||
pt.AddChange(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
HandleResource: func(pt *pagesfromdata.PagesFromTemplate, rc *pagemeta.ResourceConfig) error {
|
||||
s := pt.Site.(*Site)
|
||||
if err := rc.Compile(
|
||||
pt.GoTmplFi.Meta().PathInfo.Base(),
|
||||
s.Conf.PathParser(),
|
||||
s.conf.MediaTypes.Config,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rs := &resourceSource{path: rc.PathInfo, rc: rc, opener: nil, fi: pt.GoTmplFi, langIndex: s.languagei}
|
||||
|
||||
_, n, replaced := s.pageMap.insertResourceWithLock(rc.PathInfo.Base(), rs)
|
||||
|
||||
if h.isRebuild() && replaced {
|
||||
pt.AddChange(n.GetIdentity())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
s.pageMap.treePagesFromTemplateAdapters.Insert(pi.Base(), contentAdapter)
|
||||
|
||||
}
|
||||
|
||||
handleBuildInfo := func(s *Site, bi pagesfromdata.BuildInfo) {
|
||||
resourceCount += bi.NumResourcesAdded
|
||||
pageCount += bi.NumPagesAdded
|
||||
s.handleContentAdapterChanges(bi, buildConfig)
|
||||
}
|
||||
|
||||
bi, err := contentAdapter.Execute(context.Background())
|
||||
if err != nil {
|
||||
addErr = err
|
||||
return
|
||||
}
|
||||
handleBuildInfo(s, bi)
|
||||
|
||||
if !rebuild && bi.EnableAllLanguages {
|
||||
// Clone and insert the adapter for the other sites.
|
||||
for _, ss := range s.h.Sites {
|
||||
if s == ss {
|
||||
continue
|
||||
}
|
||||
|
||||
clone := contentAdapter.CloneForSite(ss)
|
||||
|
||||
// Make sure it gets executed for the first time.
|
||||
bi, err := clone.Execute(context.Background())
|
||||
if err != nil {
|
||||
addErr = err
|
||||
return
|
||||
}
|
||||
handleBuildInfo(ss, bi)
|
||||
|
||||
// Insert into the correct language tree so it get rebuilt on changes.
|
||||
ss.pageMap.treePagesFromTemplateAdapters.Insert(pi.Base(), clone)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// The home page is represented with the zero string.
|
||||
|
||||
+279
-139
@@ -34,7 +34,9 @@ import (
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
"github.com/gohugoio/hugo/hugofs/files"
|
||||
"github.com/gohugoio/hugo/hugolib/doctree"
|
||||
"github.com/gohugoio/hugo/hugolib/pagesfromdata"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/media"
|
||||
"github.com/gohugoio/hugo/output"
|
||||
"github.com/gohugoio/hugo/resources"
|
||||
"github.com/spf13/cast"
|
||||
@@ -93,12 +95,15 @@ 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]]
|
||||
contentTableOfContents *dynacache.Partition[string, *resources.StaleValue[contentTableOfContents]]
|
||||
|
||||
contentDataFileSeenItems *maps.Cache[string, map[uint64]bool]
|
||||
|
||||
cfg contentMapConfig
|
||||
}
|
||||
|
||||
@@ -121,6 +126,10 @@ type pageTrees struct {
|
||||
// This tree contains all taxonomy entries, e.g "/tags/blue/page1"
|
||||
treeTaxonomyEntries *doctree.TreeShiftTree[*weightedContentNode]
|
||||
|
||||
// Stores the state for _content.gotmpl files.
|
||||
// Mostly releveant for rebuilds.
|
||||
treePagesFromTemplateAdapters *doctree.TreeShiftTree[*pagesfromdata.PagesFromTemplate]
|
||||
|
||||
// A slice of the resource trees.
|
||||
resourceTrees doctree.MutableTrees
|
||||
}
|
||||
@@ -130,49 +139,48 @@ type pageTrees struct {
|
||||
// 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 {
|
||||
ids := t.collectAndMarkStaleIdentitiesFor(p.Base())
|
||||
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.
|
||||
key := p.ForBundleType(paths.PathTypeContentResource).Base()
|
||||
tree := t.treeResources
|
||||
if n := tree.Get(key); n != nil {
|
||||
n.ForEeachIdentity(func(id identity.Identity) bool {
|
||||
ids = append(ids, id)
|
||||
return false
|
||||
})
|
||||
if n, ok := tree.GetRaw(key); ok {
|
||||
n.MarkStale()
|
||||
}
|
||||
}
|
||||
}
|
||||
return ids
|
||||
}
|
||||
tree = t.treeResources
|
||||
nCount = 0
|
||||
tree.ForEeachInDimension(key, doctree.DimensionLanguage.Index(),
|
||||
cb,
|
||||
)
|
||||
|
||||
func (t *pageTrees) collectAndMarkStaleIdentitiesFor(key string) []identity.Identity {
|
||||
var ids []identity.Identity
|
||||
tree := t.treePages
|
||||
if n := tree.Get(key); n != nil {
|
||||
n.ForEeachIdentity(func(id identity.Identity) bool {
|
||||
ids = append(ids, id)
|
||||
return false
|
||||
})
|
||||
if n, ok := tree.GetRaw(key); ok {
|
||||
n.MarkStale()
|
||||
}
|
||||
}
|
||||
|
||||
tree = t.treeResources
|
||||
if n := tree.Get(key); n != nil {
|
||||
n.ForEeachIdentity(func(id identity.Identity) bool {
|
||||
ids = append(ids, id)
|
||||
return false
|
||||
})
|
||||
if n, ok := tree.GetRaw(key); ok {
|
||||
n.MarkStale()
|
||||
}
|
||||
}
|
||||
|
||||
return ids
|
||||
}
|
||||
|
||||
@@ -222,10 +230,23 @@ func (t pageTrees) Shape(d, v int) *pageTrees {
|
||||
t.treePages = t.treePages.Shape(d, v)
|
||||
t.treeResources = t.treeResources.Shape(d, v)
|
||||
t.treeTaxonomyEntries = t.treeTaxonomyEntries.Shape(d, v)
|
||||
t.treePagesFromTemplateAdapters = t.treePagesFromTemplateAdapters.Shape(d, v)
|
||||
t.createMutableTrees()
|
||||
|
||||
return &t
|
||||
}
|
||||
|
||||
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{}
|
||||
@@ -313,15 +334,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 (
|
||||
@@ -337,31 +362,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())
|
||||
@@ -389,7 +411,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 {
|
||||
@@ -426,7 +448,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(
|
||||
@@ -476,12 +498,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)
|
||||
}
|
||||
@@ -531,7 +558,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
|
||||
}
|
||||
@@ -569,9 +596,9 @@ func (m *pageMap) getOrCreateResourcesForPage(ps *pageState) resource.Resources
|
||||
|
||||
sort.SliceStable(res, lessFunc)
|
||||
|
||||
if len(ps.m.pageConfig.Resources) > 0 {
|
||||
if len(ps.m.pageConfig.ResourcesMeta) > 0 {
|
||||
for i, r := range res {
|
||||
res[i] = resources.CloneWithMetadataIfNeeded(ps.m.pageConfig.Resources, r)
|
||||
res[i] = resources.CloneWithMetadataFromMapIfNeeded(ps.m.pageConfig.ResourcesMeta, r)
|
||||
}
|
||||
sort.SliceStable(res, lessFunc)
|
||||
}
|
||||
@@ -649,12 +676,13 @@ type contentNodeShifter struct {
|
||||
numLanguages int
|
||||
}
|
||||
|
||||
func (s *contentNodeShifter) Delete(n contentNodeI, dimension doctree.Dimension) (bool, bool) {
|
||||
func (s *contentNodeShifter) Delete(n contentNodeI, dimension doctree.Dimension) (contentNodeI, bool, bool) {
|
||||
lidx := dimension[0]
|
||||
switch v := n.(type) {
|
||||
case contentNodeIs:
|
||||
resource.MarkStale(v[lidx])
|
||||
wasDeleted := v[lidx] != nil
|
||||
deleted := v[lidx]
|
||||
resource.MarkStale(deleted)
|
||||
wasDeleted := deleted != nil
|
||||
v[lidx] = nil
|
||||
isEmpty := true
|
||||
for _, vv := range v {
|
||||
@@ -663,10 +691,11 @@ func (s *contentNodeShifter) Delete(n contentNodeI, dimension doctree.Dimension)
|
||||
break
|
||||
}
|
||||
}
|
||||
return wasDeleted, isEmpty
|
||||
return deleted, wasDeleted, isEmpty
|
||||
case resourceSources:
|
||||
resource.MarkStale(v[lidx])
|
||||
wasDeleted := v[lidx] != nil
|
||||
deleted := v[lidx]
|
||||
resource.MarkStale(deleted)
|
||||
wasDeleted := deleted != nil
|
||||
v[lidx] = nil
|
||||
isEmpty := true
|
||||
for _, vv := range v {
|
||||
@@ -675,13 +704,19 @@ func (s *contentNodeShifter) Delete(n contentNodeI, dimension doctree.Dimension)
|
||||
break
|
||||
}
|
||||
}
|
||||
return wasDeleted, isEmpty
|
||||
return deleted, wasDeleted, isEmpty
|
||||
case *resourceSource:
|
||||
if lidx != v.LangIndex() {
|
||||
return nil, false, false
|
||||
}
|
||||
resource.MarkStale(v)
|
||||
return true, true
|
||||
return v, true, true
|
||||
case *pageState:
|
||||
if lidx != v.s.languagei {
|
||||
return nil, false, false
|
||||
}
|
||||
resource.MarkStale(v)
|
||||
return true, true
|
||||
return v, true, true
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown type %T", n))
|
||||
}
|
||||
@@ -754,7 +789,7 @@ func (s *contentNodeShifter) ForEeachInDimension(n contentNodeI, d int, f func(c
|
||||
}
|
||||
}
|
||||
|
||||
func (s *contentNodeShifter) InsertInto(old, new contentNodeI, dimension doctree.Dimension) contentNodeI {
|
||||
func (s *contentNodeShifter) InsertInto(old, new contentNodeI, dimension doctree.Dimension) (contentNodeI, contentNodeI, bool) {
|
||||
langi := dimension[doctree.DimensionLanguage.Index()]
|
||||
switch vv := old.(type) {
|
||||
case *pageState:
|
||||
@@ -763,37 +798,39 @@ func (s *contentNodeShifter) InsertInto(old, new contentNodeI, dimension doctree
|
||||
panic(fmt.Sprintf("unknown type %T", new))
|
||||
}
|
||||
if vv.s.languagei == newp.s.languagei && newp.s.languagei == langi {
|
||||
return new
|
||||
return new, vv, true
|
||||
}
|
||||
is := make(contentNodeIs, s.numLanguages)
|
||||
is[vv.s.languagei] = old
|
||||
is[langi] = new
|
||||
return is
|
||||
return is, old, false
|
||||
case contentNodeIs:
|
||||
oldv := vv[langi]
|
||||
vv[langi] = new
|
||||
return vv
|
||||
return vv, oldv, oldv != nil
|
||||
case resourceSources:
|
||||
oldv := vv[langi]
|
||||
vv[langi] = new.(*resourceSource)
|
||||
return vv
|
||||
return vv, oldv, oldv != nil
|
||||
case *resourceSource:
|
||||
newp, ok := new.(*resourceSource)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("unknown type %T", new))
|
||||
}
|
||||
if vv.LangIndex() == newp.LangIndex() && newp.LangIndex() == langi {
|
||||
return new
|
||||
return new, vv, true
|
||||
}
|
||||
rs := make(resourceSources, s.numLanguages)
|
||||
rs[vv.LangIndex()] = vv
|
||||
rs[langi] = newp
|
||||
return rs
|
||||
return rs, vv, false
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown type %T", old))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *contentNodeShifter) Insert(old, new contentNodeI) contentNodeI {
|
||||
func (s *contentNodeShifter) Insert(old, new contentNodeI) (contentNodeI, contentNodeI, bool) {
|
||||
switch vv := old.(type) {
|
||||
case *pageState:
|
||||
newp, ok := new.(*pageState)
|
||||
@@ -801,40 +838,52 @@ func (s *contentNodeShifter) Insert(old, new contentNodeI) contentNodeI {
|
||||
panic(fmt.Sprintf("unknown type %T", new))
|
||||
}
|
||||
if vv.s.languagei == newp.s.languagei {
|
||||
return new
|
||||
if newp != old {
|
||||
resource.MarkStale(old)
|
||||
}
|
||||
return new, vv, true
|
||||
}
|
||||
is := make(contentNodeIs, s.numLanguages)
|
||||
is[newp.s.languagei] = new
|
||||
is[vv.s.languagei] = old
|
||||
return is
|
||||
return is, old, false
|
||||
case contentNodeIs:
|
||||
newp, ok := new.(*pageState)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("unknown type %T", new))
|
||||
}
|
||||
resource.MarkStale(vv[newp.s.languagei])
|
||||
oldp := vv[newp.s.languagei]
|
||||
if oldp != newp {
|
||||
resource.MarkStale(oldp)
|
||||
}
|
||||
vv[newp.s.languagei] = new
|
||||
return vv
|
||||
return vv, oldp, oldp != nil
|
||||
case *resourceSource:
|
||||
newp, ok := new.(*resourceSource)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("unknown type %T", new))
|
||||
}
|
||||
if vv.LangIndex() == newp.LangIndex() {
|
||||
return new
|
||||
if vv != newp {
|
||||
resource.MarkStale(vv)
|
||||
}
|
||||
return new, vv, true
|
||||
}
|
||||
rs := make(resourceSources, s.numLanguages)
|
||||
rs[newp.LangIndex()] = newp
|
||||
rs[vv.LangIndex()] = vv
|
||||
return rs
|
||||
return rs, vv, false
|
||||
case resourceSources:
|
||||
newp, ok := new.(*resourceSource)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("unknown type %T", new))
|
||||
}
|
||||
resource.MarkStale(vv[newp.LangIndex()])
|
||||
oldp := vv[newp.LangIndex()]
|
||||
if oldp != newp {
|
||||
resource.MarkStale(oldp)
|
||||
}
|
||||
vv[newp.LangIndex()] = newp
|
||||
return vv
|
||||
return vv, oldp, oldp != nil
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown type %T", old))
|
||||
}
|
||||
@@ -847,12 +896,15 @@ 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}),
|
||||
contentTableOfContents: dynacache.GetOrCreatePartition[string, *resources.StaleValue[contentTableOfContents]](mcache, fmt.Sprintf("/cont/toc/%d", i), dynacache.OptionsPartition{Weight: 70, ClearWhen: dynacache.ClearOnChange}),
|
||||
|
||||
contentDataFileSeenItems: maps.NewCache[string, map[uint64]bool](),
|
||||
|
||||
cfg: contentMapConfig{
|
||||
lang: s.Lang(),
|
||||
taxonomyConfig: taxonomiesConfig.Values(),
|
||||
@@ -880,8 +932,8 @@ func newPageMap(i int, s *Site, mcache *dynacache.Cache, pageTrees *pageTrees) *
|
||||
LockType: doctree.LockTypeRead,
|
||||
Handle: func(s string, n contentNodeI, match doctree.DimensionFlag) (bool, error) {
|
||||
p := n.(*pageState)
|
||||
if p.File() != nil {
|
||||
add(p.File().FileInfo().Meta().PathInfo.BaseNameNoIdentifier(), p)
|
||||
if p.PathInfo() != nil {
|
||||
add(p.PathInfo().BaseNameNoIdentifier(), p)
|
||||
}
|
||||
return false, nil
|
||||
},
|
||||
@@ -923,9 +975,7 @@ type contentTreeReverseIndexMap struct {
|
||||
|
||||
type sitePagesAssembler struct {
|
||||
*Site
|
||||
watching bool
|
||||
incomingChanges *whatChanged
|
||||
assembleChanges *whatChanged
|
||||
assembleChanges *WhatChanged
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
@@ -1029,7 +1079,7 @@ func (h *HugoSites) resolveAndClearStateForIdentities(
|
||||
)
|
||||
|
||||
for _, id := range changes {
|
||||
if staler, ok := id.(resource.Staler); ok && !staler.IsStale() {
|
||||
if staler, ok := id.(resource.Staler); ok {
|
||||
var msgDetail string
|
||||
if p, ok := id.(*pageState); ok && p.File() != nil {
|
||||
msgDetail = fmt.Sprintf(" (%s)", p.File().Filename())
|
||||
@@ -1043,6 +1093,7 @@ func (h *HugoSites) resolveAndClearStateForIdentities(
|
||||
// 1. Handle the cache busters first, as those may produce identities for the page reset step.
|
||||
// 2. Then reset the page outputs, which may mark some resources as stale.
|
||||
// 3. Then GC the cache.
|
||||
// TOOD1
|
||||
if cachebuster != nil {
|
||||
if err := loggers.TimeTrackfn(func() (logg.LevelLogger, error) {
|
||||
ll := l.WithField("substep", "gc dynacache cachebuster")
|
||||
@@ -1059,7 +1110,7 @@ func (h *HugoSites) resolveAndClearStateForIdentities(
|
||||
return b
|
||||
}
|
||||
|
||||
h.MemCache.ClearMatching(shouldDelete)
|
||||
h.MemCache.ClearMatching(nil, shouldDelete)
|
||||
|
||||
return ll, nil
|
||||
}); err != nil {
|
||||
@@ -1067,7 +1118,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...)
|
||||
@@ -1088,6 +1139,33 @@ func (h *HugoSites) resolveAndClearStateForIdentities(
|
||||
}
|
||||
changes = changes[:n]
|
||||
|
||||
if h.pageTrees.treePagesFromTemplateAdapters.LenRaw() > 0 {
|
||||
if err := loggers.TimeTrackfn(func() (logg.LevelLogger, error) {
|
||||
ll := l.WithField("substep", "resolve content adapter change set").WithField("changes", len(changes))
|
||||
checkedCount := 0
|
||||
matchCount := 0
|
||||
depsFinder := identity.NewFinder(identity.FinderConfig{})
|
||||
|
||||
h.pageTrees.treePagesFromTemplateAdapters.WalkPrefixRaw(doctree.LockTypeRead, "",
|
||||
func(s string, n *pagesfromdata.PagesFromTemplate) (bool, error) {
|
||||
for _, id := range changes {
|
||||
checkedCount++
|
||||
if r := depsFinder.Contains(id, n.DependencyManager, 2); r > identity.FinderNotFound {
|
||||
n.MarkStale()
|
||||
matchCount++
|
||||
break
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
|
||||
ll = ll.WithField("checked", checkedCount).WithField("matches", matchCount)
|
||||
return ll, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := loggers.TimeTrackfn(func() (logg.LevelLogger, error) {
|
||||
// changesLeft: The IDs that the pages is dependent on.
|
||||
// changesRight: The IDs that the pages depend on.
|
||||
@@ -1232,6 +1310,7 @@ func (sa *sitePagesAssembler) applyAggregates() error {
|
||||
rw := pw.Extend()
|
||||
rw.Tree = sa.pageMap.treeResources
|
||||
sa.lastmod = time.Time{}
|
||||
rebuild := sa.s.h.isRebuild()
|
||||
|
||||
pw.Handle = func(keyPage string, n contentNodeI, match doctree.DimensionFlag) (bool, error) {
|
||||
pageBundle := n.(*pageState)
|
||||
@@ -1252,7 +1331,7 @@ func (sa *sitePagesAssembler) applyAggregates() error {
|
||||
// Home page gets it's cascade from the site config.
|
||||
cascade = sa.conf.Cascade.Config
|
||||
|
||||
if pageBundle.m.pageConfig.Cascade == nil {
|
||||
if pageBundle.m.pageConfig.CascadeCompiled == nil {
|
||||
// Pass the site cascade downwards.
|
||||
pw.WalkContext.Data().Insert(keyPage, cascade)
|
||||
}
|
||||
@@ -1263,18 +1342,20 @@ func (sa *sitePagesAssembler) applyAggregates() error {
|
||||
}
|
||||
}
|
||||
|
||||
if (pageBundle.IsHome() || pageBundle.IsSection()) && pageBundle.m.setMetaPostCount > 0 {
|
||||
oldDates := pageBundle.m.pageConfig.Dates
|
||||
if rebuild {
|
||||
if (pageBundle.IsHome() || pageBundle.IsSection()) && pageBundle.m.setMetaPostCount > 0 {
|
||||
oldDates := pageBundle.m.pageConfig.Dates
|
||||
|
||||
// We need to wait until after the walk to determine if any of the dates have changed.
|
||||
pw.WalkContext.AddPostHook(
|
||||
func() error {
|
||||
if oldDates != pageBundle.m.pageConfig.Dates {
|
||||
sa.assembleChanges.Add(pageBundle)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
// We need to wait until after the walk to determine if any of the dates have changed.
|
||||
pw.WalkContext.AddPostHook(
|
||||
func() error {
|
||||
if oldDates != pageBundle.m.pageConfig.Dates {
|
||||
sa.assembleChanges.Add(pageBundle)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Combine the cascade map with front matter.
|
||||
@@ -1284,15 +1365,15 @@ func (sa *sitePagesAssembler) applyAggregates() error {
|
||||
|
||||
// We receive cascade values from above. If this leads to a change compared
|
||||
// to the previous value, we need to mark the page and its dependencies as changed.
|
||||
if pageBundle.m.setMetaPostCascadeChanged {
|
||||
if rebuild && pageBundle.m.setMetaPostCascadeChanged {
|
||||
sa.assembleChanges.Add(pageBundle)
|
||||
}
|
||||
|
||||
const eventName = "dates"
|
||||
if n.isContentNodeBranch() {
|
||||
if pageBundle.m.pageConfig.Cascade != nil {
|
||||
if pageBundle.m.pageConfig.CascadeCompiled != nil {
|
||||
// Pass it down.
|
||||
pw.WalkContext.Data().Insert(keyPage, pageBundle.m.pageConfig.Cascade)
|
||||
pw.WalkContext.Data().Insert(keyPage, pageBundle.m.pageConfig.CascadeCompiled)
|
||||
}
|
||||
|
||||
wasZeroDates := pageBundle.m.pageConfig.Dates.IsAllDatesZero()
|
||||
@@ -1393,9 +1474,9 @@ func (sa *sitePagesAssembler) applyAggregatesToTaxonomiesAndTerms() error {
|
||||
p := n.(*pageState)
|
||||
if p.Kind() != kinds.KindTerm {
|
||||
// The other kinds were handled in applyAggregates.
|
||||
if p.m.pageConfig.Cascade != nil {
|
||||
if p.m.pageConfig.CascadeCompiled != nil {
|
||||
// Pass it down.
|
||||
pw.WalkContext.Data().Insert(s, p.m.pageConfig.Cascade)
|
||||
pw.WalkContext.Data().Insert(s, p.m.pageConfig.CascadeCompiled)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1486,13 +1567,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
|
||||
}
|
||||
@@ -1523,7 +1597,7 @@ func (sa *sitePagesAssembler) assembleTermsAndTranslations() error {
|
||||
singular: viewName.singular,
|
||||
s: sa.Site,
|
||||
pathInfo: pi,
|
||||
pageMetaParams: pageMetaParams{
|
||||
pageMetaParams: &pageMetaParams{
|
||||
pageConfig: &pagemeta.PageConfig{
|
||||
Kind: kinds.KindTerm,
|
||||
},
|
||||
@@ -1535,8 +1609,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 == "" {
|
||||
@@ -1571,18 +1647,25 @@ 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()
|
||||
baseTarget := targetPaths.SubResourceBaseTarget
|
||||
duplicateResourceFiles := true
|
||||
if ps.s.ContentSpec.Converters.IsGoldmark(ps.m.pageConfig.Markup) {
|
||||
if ps.m.pageConfig.ContentMediaType.IsMarkdown() {
|
||||
duplicateResourceFiles = ps.s.ContentSpec.Converters.GetMarkupConfig().Goldmark.DuplicateResourceFiles
|
||||
}
|
||||
|
||||
duplicateResourceFiles = duplicateResourceFiles || ps.s.Conf.IsMultihost()
|
||||
|
||||
sa.pageMap.forEachResourceInPage(
|
||||
err := sa.pageMap.forEachResourceInPage(
|
||||
ps, lockType,
|
||||
!duplicateResourceFiles,
|
||||
func(resourceKey string, n contentNodeI, match doctree.DimensionFlag) (bool, error) {
|
||||
@@ -1608,6 +1691,23 @@ func (sa *sitePagesAssembler) assembleResources() error {
|
||||
|
||||
}
|
||||
|
||||
if rs.rc != nil && rs.rc.Content.IsResourceValue() {
|
||||
if rs.rc.Name == "" {
|
||||
rs.rc.Name = relPathOriginal
|
||||
}
|
||||
r, err := ps.m.s.ResourceSpec.NewResourceWrapperFromResourceConfig(rs.rc)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
rs.r = r
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var mt media.Type
|
||||
if rs.rc != nil {
|
||||
mt = rs.rc.ContentMediaType
|
||||
}
|
||||
|
||||
rd := resources.ResourceSourceDescriptor{
|
||||
OpenReadSeekCloser: rs.opener,
|
||||
Path: rs.path,
|
||||
@@ -1616,10 +1716,25 @@ func (sa *sitePagesAssembler) assembleResources() error {
|
||||
TargetBasePaths: targetBasePaths,
|
||||
BasePathRelPermalink: targetPaths.SubResourceBaseLink,
|
||||
BasePathTargetPath: baseTarget,
|
||||
Name: relPath,
|
||||
NameNormalized: relPath,
|
||||
NameOriginal: relPathOriginal,
|
||||
MediaType: mt,
|
||||
LazyPublish: !ps.m.pageConfig.Build.PublishResources,
|
||||
}
|
||||
|
||||
if rs.rc != nil {
|
||||
rc := rs.rc
|
||||
rd.OpenReadSeekCloser = rc.Content.ValueAsOpenReadSeekCloser()
|
||||
if rc.Name != "" {
|
||||
rd.NameNormalized = rc.Name
|
||||
rd.NameOriginal = rc.Name
|
||||
}
|
||||
if rc.Title != "" {
|
||||
rd.Title = rc.Title
|
||||
}
|
||||
rd.Params = rc.Params
|
||||
}
|
||||
|
||||
r, err := ps.m.s.ResourceSpec.NewResource(rd)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -1629,7 +1744,7 @@ func (sa *sitePagesAssembler) assembleResources() error {
|
||||
},
|
||||
)
|
||||
|
||||
return false, nil
|
||||
return false, err
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1662,6 +1777,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
|
||||
}
|
||||
@@ -1695,6 +1815,10 @@ func (sa *sitePagesAssembler) removeShouldNotBuild() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sa.pageMap.DeletePageAndResourcesBelow(keys...)
|
||||
|
||||
return nil
|
||||
@@ -1727,7 +1851,7 @@ func (sa *sitePagesAssembler) addStandalonePages() error {
|
||||
m := &pageMeta{
|
||||
s: s,
|
||||
pathInfo: s.Conf.PathParser().Parse(files.ComponentFolderContent, key+f.MediaType.FirstSuffix.FullSuffix),
|
||||
pageMetaParams: pageMetaParams{
|
||||
pageMetaParams: &pageMetaParams{
|
||||
pageConfig: &pagemeta.PageConfig{
|
||||
Kind: kind,
|
||||
},
|
||||
@@ -1744,7 +1868,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1757,11 +1881,19 @@ func (sa *sitePagesAssembler) addStandalonePages() error {
|
||||
}
|
||||
|
||||
if sitemapEnabled {
|
||||
addStandalone("/sitemap", kinds.KindSitemap, output.SitemapFormat)
|
||||
skipSitemapIndex := s.Conf.IsMultihost() || !(s.Conf.DefaultContentLanguageInSubdir() || s.Conf.IsMultiLingual())
|
||||
of := output.SitemapFormat
|
||||
if s.conf.Sitemap.Filename != "" {
|
||||
of.BaseName = paths.Filename(s.conf.Sitemap.Filename)
|
||||
}
|
||||
addStandalone("/_sitemap", kinds.KindSitemap, of)
|
||||
|
||||
skipSitemapIndex := s.Conf.IsMultihost() || !(s.Conf.DefaultContentLanguageInSubdir() || s.Conf.IsMultilingual())
|
||||
if !skipSitemapIndex {
|
||||
addStandalone("/sitemapindex", kinds.KindSitemapIndex, output.SitemapIndexFormat)
|
||||
of = output.SitemapIndexFormat
|
||||
if s.conf.Sitemap.Filename != "" {
|
||||
of.BaseName = paths.Filename(s.conf.Sitemap.Filename)
|
||||
}
|
||||
addStandalone("/_sitemapindex", kinds.KindSitemapIndex, of)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1794,6 +1926,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] {
|
||||
@@ -1837,7 +1977,7 @@ func (sa *sitePagesAssembler) addMissingRootSections() error {
|
||||
m := &pageMeta{
|
||||
s: sa.Site,
|
||||
pathInfo: p,
|
||||
pageMetaParams: pageMetaParams{
|
||||
pageMetaParams: &pageMetaParams{
|
||||
pageConfig: &pagemeta.PageConfig{
|
||||
Kind: kinds.KindHome,
|
||||
},
|
||||
@@ -1847,7 +1987,7 @@ func (sa *sitePagesAssembler) addMissingRootSections() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.Tree.InsertWithLock(p.Base(), n)
|
||||
w.Tree.InsertIntoValuesDimensionWithLock(p.Base(), n)
|
||||
sa.home = n
|
||||
}
|
||||
|
||||
@@ -1870,7 +2010,7 @@ func (sa *sitePagesAssembler) addMissingTaxonomies() error {
|
||||
m := &pageMeta{
|
||||
s: sa.Site,
|
||||
pathInfo: sa.Conf.PathParser().Parse(files.ComponentFolderContent, key+"/_index.md"),
|
||||
pageMetaParams: pageMetaParams{
|
||||
pageMetaParams: &pageMetaParams{
|
||||
pageConfig: &pagemeta.PageConfig{
|
||||
Kind: kinds.KindTaxonomy,
|
||||
},
|
||||
|
||||
+82
-11
@@ -16,6 +16,7 @@ package hugolib
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
@@ -27,7 +28,7 @@ func TestContentMapSite(t *testing.T) {
|
||||
pageTempl := `
|
||||
---
|
||||
title: "Page %d"
|
||||
date: "2019-06-0%d"
|
||||
date: "2019-06-0%d"
|
||||
lastMod: "2019-06-0%d"
|
||||
categories: [%q]
|
||||
---
|
||||
@@ -54,7 +55,7 @@ draft: true
|
||||
title: "Hugo Home"
|
||||
cascade:
|
||||
description: "Common Description"
|
||||
|
||||
|
||||
---
|
||||
|
||||
Home Content.
|
||||
@@ -110,7 +111,7 @@ IsDescendant overlap2: false: {{ $overlap2.IsDescendant $overlap1 }}
|
||||
IsAncestor overlap1: false: {{ $overlap1.IsAncestor $overlap2 }}
|
||||
IsAncestor overlap2: false: {{ $overlap2.IsAncestor $overlap1 }}
|
||||
FirstSection: {{ $blogSub.FirstSection.RelPermalink }} {{ $blog.FirstSection.RelPermalink }} {{ $home.FirstSection.RelPermalink }} {{ $page.FirstSection.RelPermalink }}
|
||||
InSection: true: {{ $page.InSection $blog }} false: {{ $page.InSection $blogSub }}
|
||||
InSection: true: {{ $page.InSection $blog }} false: {{ $page.InSection $blogSub }}
|
||||
Next: {{ $page2.Next.RelPermalink }}
|
||||
NextInSection: {{ $page2.NextInSection.RelPermalink }}
|
||||
Pages: {{ range $blog.Pages }}{{ .RelPermalink }}|{{ end }}
|
||||
@@ -140,11 +141,11 @@ Draft5: {{ if (.Site.GetPage "blog/draftsection/sub/page") }}FOUND{{ end }}|
|
||||
Num Regular: 9
|
||||
Main Sections: [blog]
|
||||
Pag Num Pages: 9
|
||||
|
||||
Home: Hugo Home|/|2019-06-08|Current Section: /|Resources:
|
||||
Blog Section: Blogs|/blog/|2019-06-08|Current Section: /blog|Resources:
|
||||
|
||||
Home: Hugo Home|/|2019-06-08|Current Section: /|Resources:
|
||||
Blog Section: Blogs|/blog/|2019-06-08|Current Section: /blog|Resources:
|
||||
Blog Sub Section: Page 3|/blog/subsection/|2019-06-03|Current Section: /blog/subsection|Resources: application: /blog/subsection/subdata.json|
|
||||
Page: Page 1|/blog/page1/|2019-06-01|Current Section: /blog|Resources:
|
||||
Page: Page 1|/blog/page1/|2019-06-01|Current Section: /blog|Resources:
|
||||
Bundle: Page 12|/blog/bundle/|0001-01-01|Current Section: /blog|Resources: application: /blog/bundle/data.json|page: |
|
||||
IsDescendant: true: true true: true true: true true: true true: true false: false false: false
|
||||
IsAncestor: true: true true: true true: true true: true true: true true: true false: false false: false false: false false: false
|
||||
@@ -153,7 +154,7 @@ Draft5: {{ if (.Site.GetPage "blog/draftsection/sub/page") }}FOUND{{ end }}|
|
||||
IsAncestor overlap1: false: false
|
||||
IsAncestor overlap2: false: false
|
||||
FirstSection: /blog/ /blog/ / /blog/
|
||||
InSection: true: true false: false
|
||||
InSection: true: true false: false
|
||||
Next: /blog/page3/
|
||||
NextInSection: /blog/page3/
|
||||
Pages: /blog/page3/|/blog/subsection/|/blog/page2/|/blog/page1/|/blog/bundle/|
|
||||
@@ -165,13 +166,13 @@ Draft5: {{ if (.Site.GetPage "blog/draftsection/sub/page") }}FOUND{{ end }}|
|
||||
Pag Blog Num Pages: 4
|
||||
Blog Num RegularPages: 4
|
||||
Blog Num Pages: 5
|
||||
|
||||
|
||||
Draft1: |
|
||||
Draft2: FOUND|
|
||||
Draft3: FOUND|
|
||||
Draft4: FOUND|
|
||||
Draft5: FOUND|
|
||||
|
||||
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -185,7 +186,7 @@ title = "Integration Test"
|
||||
disableKinds=["page", "section", "taxonomy", "term", "sitemap", "robotsTXT", "RSS"]
|
||||
-- layouts/index.html --
|
||||
Home: {{ .Title }}|
|
||||
|
||||
|
||||
`
|
||||
|
||||
b := NewIntegrationTestBuilder(
|
||||
@@ -326,3 +327,73 @@ 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)
|
||||
}
|
||||
|
||||
func TestSitemapOverrideFilename(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
baseURL = 'https://example.org/'
|
||||
disableKinds = ['page','rss','section','taxonomy','term']
|
||||
defaultContentLanguage = 'de'
|
||||
defaultContentLanguageInSubdir = true
|
||||
[languages.de]
|
||||
[languages.en]
|
||||
[sitemap]
|
||||
filename = 'foo.xml'
|
||||
-- layouts/index.html --
|
||||
irrelevant
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileExists("public/de/foo.xml", true)
|
||||
b.AssertFileExists("public/en/foo.xml", true)
|
||||
b.AssertFileContent("public/foo.xml",
|
||||
"<loc>https://example.org/de/foo.xml</loc>",
|
||||
"<loc>https://example.org/en/foo.xml</loc>",
|
||||
)
|
||||
|
||||
files = strings.ReplaceAll(files, "filename = 'foo.xml'", "")
|
||||
|
||||
b = Test(t, files)
|
||||
|
||||
b.AssertFileExists("public/de/sitemap.xml", true)
|
||||
b.AssertFileExists("public/en/sitemap.xml", true)
|
||||
b.AssertFileContent("public/sitemap.xml",
|
||||
"<loc>https://example.org/de/sitemap.xml</loc>",
|
||||
"<loc>https://example.org/en/sitemap.xml</loc>",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
package hugolib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -241,3 +242,52 @@ iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAA
|
||||
"p1|<p><a href=\"p2\">P2</a>", "<img src=\"pixel.png\" alt=\"Pixel\">")
|
||||
})
|
||||
}
|
||||
|
||||
func TestRenderHooksDefaultEscape(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
[markup.goldmark.renderHooks]
|
||||
[markup.goldmark.renderHooks.image]
|
||||
enableDefault = ENABLE
|
||||
[markup.goldmark.renderHooks.link]
|
||||
enableDefault = ENABLE
|
||||
[markup.goldmark.parser]
|
||||
wrapStandAloneImageWithinParagraph = false
|
||||
[markup.goldmark.parser.attribute]
|
||||
block = true
|
||||
title = true
|
||||
-- content/_index.md --
|
||||
---
|
||||
title: "Home"
|
||||
---
|
||||
Link: [text-"<>&](/destination-"<> 'title-"<>&')
|
||||
|
||||
Image: 
|
||||
{class="><script>alert()</script>" id="baz"}
|
||||
|
||||
-- layouts/index.html --
|
||||
{{ .Content }}
|
||||
`
|
||||
|
||||
for _, enabled := range []bool{true, false} {
|
||||
enabled := enabled
|
||||
t.Run(fmt.Sprint(enabled), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
b := Test(t, strings.ReplaceAll(files, "ENABLE", fmt.Sprint(enabled)))
|
||||
|
||||
// The escaping is slightly different between the two.
|
||||
if enabled {
|
||||
b.AssertFileContent("public/index.html",
|
||||
"Link: <a href=\"/destination-%22%3C%3E\" title=\"title-"<>&\">text-"<>&</a>",
|
||||
"img alt=\"alt-"<>&\" src=\"/destination-%22%3C%3E\" title=\"title-"<>&\">",
|
||||
"><script>",
|
||||
)
|
||||
} else {
|
||||
b.AssertFileContent("public/index.html",
|
||||
"Link: <a href=\"/destination-%22%3C%3E\" title=\"title-"<>&\">text-"<>&</a>",
|
||||
"Image: <img src=\"/destination-%22%3C%3E\" alt=\"alt-"<>&\" title=\"title-"<>&\">",
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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", "||")
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ func TestTreeInsert(t *testing.T) {
|
||||
c.Assert(tree.Get("/notfound"), qt.IsNil)
|
||||
|
||||
ab2 := &testValue{ID: "/a/b", Lang: 0}
|
||||
v, ok := tree.InsertIntoValuesDimension("/a/b", ab2)
|
||||
v, _, ok := tree.InsertIntoValuesDimension("/a/b", ab2)
|
||||
c.Assert(ok, qt.IsTrue)
|
||||
c.Assert(v, qt.DeepEquals, ab2)
|
||||
|
||||
@@ -239,16 +239,16 @@ func (s *testShifter) ForEeachInDimension(n *testValue, d int, f func(n *testVal
|
||||
f(n)
|
||||
}
|
||||
|
||||
func (s *testShifter) Insert(old, new *testValue) *testValue {
|
||||
return new
|
||||
func (s *testShifter) Insert(old, new *testValue) (*testValue, *testValue, bool) {
|
||||
return new, old, true
|
||||
}
|
||||
|
||||
func (s *testShifter) InsertInto(old, new *testValue, dimension doctree.Dimension) *testValue {
|
||||
return new
|
||||
func (s *testShifter) InsertInto(old, new *testValue, dimension doctree.Dimension) (*testValue, *testValue, bool) {
|
||||
return new, old, true
|
||||
}
|
||||
|
||||
func (s *testShifter) Delete(n *testValue, dimension doctree.Dimension) (bool, bool) {
|
||||
return true, true
|
||||
func (s *testShifter) Delete(n *testValue, dimension doctree.Dimension) (*testValue, bool, bool) {
|
||||
return nil, true, true
|
||||
}
|
||||
|
||||
func (s *testShifter) Shift(n *testValue, dimension doctree.Dimension, exact bool) (*testValue, bool, doctree.DimensionFlag) {
|
||||
|
||||
@@ -38,16 +38,18 @@ type (
|
||||
|
||||
// Insert inserts new into the tree into the dimension it provides.
|
||||
// It may replace old.
|
||||
// It returns a T (can be the same as old).
|
||||
Insert(old, new T) T
|
||||
// It returns the updated and existing T
|
||||
// and a bool indicating if an existing record is updated.
|
||||
Insert(old, new T) (T, T, bool)
|
||||
|
||||
// Insert inserts new into the given dimension.
|
||||
// It may replace old.
|
||||
// It returns a T (can be the same as old).
|
||||
InsertInto(old, new T, dimension Dimension) T
|
||||
// It returns the updated and existing T
|
||||
// and a bool indicating if an existing record is updated.
|
||||
InsertInto(old, new T, dimension Dimension) (T, T, bool)
|
||||
|
||||
// Delete deletes T from the given dimension and returns whether the dimension was deleted and if it's empty after the delete.
|
||||
Delete(v T, dimension Dimension) (bool, bool)
|
||||
// Delete deletes T from the given dimension and returns the deleted T and whether the dimension was deleted and if it's empty after the delete.
|
||||
Delete(v T, dimension Dimension) (T, bool, bool)
|
||||
|
||||
// Shift shifts T into the given dimension
|
||||
// and returns the shifted T and a bool indicating if the shift was successful and
|
||||
@@ -81,7 +83,11 @@ func New[T any](cfg Config[T]) *NodeShiftTree[T] {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *NodeShiftTree[T]) Delete(key string) {
|
||||
func (r *NodeShiftTree[T]) Delete(key string) (T, bool) {
|
||||
return r.delete(key)
|
||||
}
|
||||
|
||||
func (r *NodeShiftTree[T]) DeleteRaw(key string) {
|
||||
r.delete(key)
|
||||
}
|
||||
|
||||
@@ -103,23 +109,24 @@ func (r *NodeShiftTree[T]) DeletePrefix(prefix string) int {
|
||||
return false
|
||||
})
|
||||
for _, key := range keys {
|
||||
if ok := r.delete(key); ok {
|
||||
if _, ok := r.delete(key); ok {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (r *NodeShiftTree[T]) delete(key string) bool {
|
||||
func (r *NodeShiftTree[T]) delete(key string) (T, bool) {
|
||||
var wasDeleted bool
|
||||
var deleted T
|
||||
if v, ok := r.tree.Get(key); ok {
|
||||
var isEmpty bool
|
||||
wasDeleted, isEmpty = r.shifter.Delete(v.(T), r.dims)
|
||||
deleted, wasDeleted, isEmpty = r.shifter.Delete(v.(T), r.dims)
|
||||
if isEmpty {
|
||||
r.tree.Delete(key)
|
||||
}
|
||||
}
|
||||
return wasDeleted
|
||||
return deleted, wasDeleted
|
||||
}
|
||||
|
||||
func (t *NodeShiftTree[T]) DeletePrefixAll(prefix string) int {
|
||||
@@ -141,22 +148,33 @@ func (t *NodeShiftTree[T]) Increment(d int) *NodeShiftTree[T] {
|
||||
return t.Shape(d, t.dims[d]+1)
|
||||
}
|
||||
|
||||
func (r *NodeShiftTree[T]) InsertIntoCurrentDimension(s string, v T) (T, bool) {
|
||||
func (r *NodeShiftTree[T]) InsertIntoCurrentDimension(s string, v T) (T, T, bool) {
|
||||
s = mustValidateKey(cleanKey(s))
|
||||
var (
|
||||
updated bool
|
||||
existing T
|
||||
)
|
||||
if vv, ok := r.tree.Get(s); ok {
|
||||
v = r.shifter.InsertInto(vv.(T), v, r.dims)
|
||||
v, existing, updated = r.shifter.InsertInto(vv.(T), v, r.dims)
|
||||
}
|
||||
r.tree.Insert(s, v)
|
||||
return v, true
|
||||
return v, existing, updated
|
||||
}
|
||||
|
||||
func (r *NodeShiftTree[T]) InsertIntoValuesDimension(s string, v T) (T, bool) {
|
||||
// InsertIntoValuesDimension inserts v into the tree at the given key and the
|
||||
// dimension defined by the value.
|
||||
// It returns the updated and existing T and a bool indicating if an existing record is updated.
|
||||
func (r *NodeShiftTree[T]) InsertIntoValuesDimension(s string, v T) (T, T, bool) {
|
||||
s = mustValidateKey(cleanKey(s))
|
||||
var (
|
||||
updated bool
|
||||
existing T
|
||||
)
|
||||
if vv, ok := r.tree.Get(s); ok {
|
||||
v = r.shifter.Insert(vv.(T), v)
|
||||
v, existing, updated = r.shifter.Insert(vv.(T), v)
|
||||
}
|
||||
r.tree.Insert(s, v)
|
||||
return v, true
|
||||
return v, existing, updated
|
||||
}
|
||||
|
||||
func (r *NodeShiftTree[T]) InsertRawWithLock(s string, v any) (any, bool) {
|
||||
@@ -165,7 +183,8 @@ func (r *NodeShiftTree[T]) InsertRawWithLock(s string, v any) (any, bool) {
|
||||
return r.tree.Insert(s, v)
|
||||
}
|
||||
|
||||
func (r *NodeShiftTree[T]) InsertWithLock(s string, v T) (T, bool) {
|
||||
// It returns the updated and existing T and a bool indicating if an existing record is updated.
|
||||
func (r *NodeShiftTree[T]) InsertIntoValuesDimensionWithLock(s string, v T) (T, T, bool) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
return r.InsertIntoValuesDimension(s, v)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user