mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-26 08:18:53 +00:00
Compare commits
87 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3fbcc4b7b3 | |||
| 223bf28004 | |||
| 72b0ccdb01 | |||
| 65e52a7f5c | |||
| bdf935d66c | |||
| 8ebcaa5394 | |||
| 241481931f | |||
| 5caed8a71f | |||
| d1278f696a | |||
| 92f31ae63b | |||
| fd3953c18c | |||
| 15463f835b | |||
| 466fa43c16 | |||
| 6072ce0bcd | |||
| 2c5943ddc6 | |||
| 0cb459a20a | |||
| 475f87f685 | |||
| 604cfffc5b | |||
| d863dde6c6 | |||
| 580b214a4c | |||
| ddb9547085 | |||
| 288b0fb15a | |||
| 3e1344632d | |||
| 7a9ce0eca5 | |||
| f2ba0cc8c5 | |||
| 62ceaabdce | |||
| 35fa192838 | |||
| 09ac733381 | |||
| 66da1b7b2f | |||
| 5a9ecb82a3 | |||
| cbc35c48d2 | |||
| 44f3c07969 | |||
| a5a4422aae | |||
| 617e094482 | |||
| cf12fa6161 | |||
| 2e1c81770a | |||
| 4276075c78 | |||
| d25cb2943f | |||
| 8b9bdc403d | |||
| 4e94d1db72 | |||
| 0566bbf7c7 | |||
| 534e7155bb | |||
| 953f215f32 | |||
| 8e2fd55923 | |||
| 311b8008bf | |||
| c7d5f9f067 | |||
| bfebd8c02c | |||
| 0afb4866e3 | |||
| b1ec0c226f | |||
| 212d9e3017 | |||
| 4daac654d9 | |||
| 27b077544d | |||
| 0f8343a2c2 | |||
| 3fcbee261a | |||
| db9d274275 | |||
| 95baafeac4 | |||
| e4bfe59c4e | |||
| 6f7bf3f2d7 | |||
| 9e904d756b | |||
| d2cfaede5b | |||
| 322d19a81f | |||
| 7cb484e121 | |||
| 0b395f0b41 | |||
| c1a83076bf | |||
| 0f8dc47037 | |||
| 3b478f50b7 | |||
| f343b8eb78 | |||
| 60ede146b6 | |||
| dd9eaf19fd | |||
| 46a2ea6d0d | |||
| 653ab2cc1f | |||
| 52edea0fec | |||
| 6a5acd753a | |||
| 805b21555e | |||
| 8ca7052528 | |||
| bb232a351a | |||
| 3854a6fa6c | |||
| cd0112a05a | |||
| 6f7fbe03b1 | |||
| 2fc2e9c871 | |||
| e164834f0a | |||
| d524067382 | |||
| 31ce89f7f4 | |||
| ee55fde5e5 | |||
| 2f9eac480f | |||
| 3a8189ee93 | |||
| 7bc3401eb5 |
@@ -1,6 +1,6 @@
|
||||
defaults: &defaults
|
||||
docker:
|
||||
- image: bepsays/ci-goreleaser:1.1800.000
|
||||
- image: bepsays/ci-goreleaser:1.1800.300
|
||||
environment:
|
||||
CGO_ENABLED: "0"
|
||||
|
||||
|
||||
@@ -23,9 +23,9 @@ jobs:
|
||||
with:
|
||||
operations-per-run: 999
|
||||
days-before-issue-stale: 365
|
||||
days-before-pr-stale: 200
|
||||
days-before-issue-close: 21
|
||||
days-before-pr-close: 21
|
||||
days-before-pr-stale: 365
|
||||
days-before-issue-close: 56
|
||||
days-before-pr-close: 56
|
||||
stale-issue-message: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. The resources of the Hugo team are limited, and so we are asking for your help.
|
||||
|
||||
@@ -50,6 +50,7 @@ jobs:
|
||||
- if: matrix.os == 'windows-latest'
|
||||
run: |
|
||||
choco install pandoc
|
||||
choco install mingw --version 10.2.0 --allow-downgrade
|
||||
- run: pandoc -v
|
||||
- if: matrix.os == 'ubuntu-latest'
|
||||
name: Install dart-sass-embedded Linux
|
||||
@@ -72,10 +73,11 @@ jobs:
|
||||
echo "6ae442129dbb3334bc21ef851261da6c0c1b560da790ca2e1350871d00ab816d sass_embedded-1.0.0-beta.6-windows-x64.zip" | sha256sum -c;
|
||||
unzip sass_embedded-1.0.0-beta.6-windows-x64.zip;
|
||||
echo "$env:GITHUB_WORKSPACE/sass_embedded/" | Out-File -FilePath $Env:GITHUB_PATH -Encoding utf-8 -Append
|
||||
- name: Test
|
||||
- name: Check
|
||||
run: |
|
||||
mage -v test
|
||||
mage -v check;
|
||||
env:
|
||||
HUGO_BUILD_TAGS: extended
|
||||
- name: Build Docs
|
||||
env:
|
||||
HUGO_BUILD_TAGS: extended
|
||||
|
||||
+1
-7
@@ -83,7 +83,7 @@ To make the contribution process as seamless as possible, we ask for the followi
|
||||
|
||||
### Git Commit Message Guidelines
|
||||
|
||||
This [blog article](http://chris.beams.io/posts/git-commit/) is a good resource for learning how to write good commit messages,
|
||||
This [blog article](https://cbea.ms/git-commit/) is a good resource for learning how to write good commit messages,
|
||||
the most important part being that each commit message should have a title/subject in imperative mood starting with a capital letter and no trailing period:
|
||||
*"js: Return error when option x is not set"*, **NOT** *"returning some error."*
|
||||
|
||||
@@ -99,12 +99,6 @@ Most title/subjects should have a lower-cased prefix with a colon and one whites
|
||||
Also, if your commit references one or more GitHub issues, always end your commit message body with *See #1234* or *Fixes #1234*.
|
||||
Replace *1234* with the GitHub issue ID. The last example will close the issue when the commit is merged into *master*.
|
||||
|
||||
Sometimes it makes sense to prefix the commit message with the package name (or docs folder) all lowercased ending with a colon.
|
||||
That is fine, but the rest of the rules above apply.
|
||||
So it is "tpl: Add emojify template func", not "tpl: add emojify template func.", and "docs: Document emoji", not "doc: document emoji."
|
||||
|
||||
Please use a short and descriptive branch name, e.g. **NOT** "patch-1". It's very common but creates a naming conflict each time when a submission is pulled for a review.
|
||||
|
||||
An example:
|
||||
|
||||
```text
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<a href="https://gohugo.io/"><img src="https://raw.githubusercontent.com/gohugoio/gohugoioTheme/master/static/images/hugo-logo-wide.svg?sanitize=true" alt="Hugo" width="565"></a>
|
||||
|
||||
A Fast and Flexible Static Site Generator built with love by [bep](https://github.com/bep), [spf13](http://spf13.com/) and [friends](https://github.com/gohugoio/hugo/graphs/contributors) in [Go][].
|
||||
A Fast and Flexible Static Site Generator built with love by [bep](https://github.com/bep), [spf13](https://spf13.com/) and [friends](https://github.com/gohugoio/hugo/graphs/contributors) in [Go][].
|
||||
|
||||
[Website](https://gohugo.io) |
|
||||
[Forum](https://discourse.gohugo.io) |
|
||||
@@ -130,97 +130,119 @@ Hugo stands on the shoulder of many great open source libraries.
|
||||
|
||||
If you run `hugo env -v` you will get a complete and up to date list.
|
||||
|
||||
In Hugo 0.89.0 that list is, in lexical order:
|
||||
In Hugo 0.100.1 that list is, in lexical order:
|
||||
|
||||
```
|
||||
cloud.google.com/go/storage="v1.10.0"
|
||||
cloud.google.com/go="v0.87.0"
|
||||
github.com/Azure/azure-pipeline-go="v0.2.2"
|
||||
github.com/Azure/azure-storage-blob-go="v0.9.0"
|
||||
cloud.google.com/go/compute="v1.6.1"
|
||||
cloud.google.com/go/iam="v0.3.0"
|
||||
cloud.google.com/go/storage="v1.22.0"
|
||||
cloud.google.com/go="v0.101.0"
|
||||
github.com/Azure/azure-pipeline-go="v0.2.3"
|
||||
github.com/Azure/azure-storage-blob-go="v0.14.0"
|
||||
github.com/Azure/go-autorest/autorest/adal="v0.9.15"
|
||||
github.com/Azure/go-autorest/autorest/date="v0.3.0"
|
||||
github.com/Azure/go-autorest/autorest="v0.11.20"
|
||||
github.com/Azure/go-autorest/logger="v0.2.1"
|
||||
github.com/Azure/go-autorest/tracing="v0.6.0"
|
||||
github.com/BurntSushi/locker="v0.0.0-20171006230638-a6e239ea1c69"
|
||||
github.com/BurntSushi/toml="v0.3.1"
|
||||
github.com/PuerkitoBio/purell="v1.1.1"
|
||||
github.com/PuerkitoBio/urlesc="v0.0.0-20170810143723-de5bf2ad4578"
|
||||
github.com/alecthomas/chroma="v0.9.4"
|
||||
github.com/alecthomas/chroma="v0.10.0"
|
||||
github.com/armon/go-radix="v1.0.0"
|
||||
github.com/aws/aws-sdk-go="v1.41.14"
|
||||
github.com/aws/aws-sdk-go-v2/config="v1.7.0"
|
||||
github.com/aws/aws-sdk-go-v2/credentials="v1.4.0"
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds="v1.5.0"
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini="v1.2.2"
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url="v1.3.0"
|
||||
github.com/aws/aws-sdk-go-v2/service/sso="v1.4.0"
|
||||
github.com/aws/aws-sdk-go-v2/service/sts="v1.7.0"
|
||||
github.com/aws/aws-sdk-go-v2="v1.9.0"
|
||||
github.com/aws/aws-sdk-go="v1.43.5"
|
||||
github.com/aws/smithy-go="v1.8.0"
|
||||
github.com/bep/clock="v0.3.0"
|
||||
github.com/bep/debounce="v1.2.0"
|
||||
github.com/bep/gitmap="v1.1.2"
|
||||
github.com/bep/godartsass="v0.12.0"
|
||||
github.com/bep/golibsass="v1.0.0"
|
||||
github.com/bep/goat="v0.5.0"
|
||||
github.com/bep/godartsass="v0.14.0"
|
||||
github.com/bep/golibsass="v1.1.0"
|
||||
github.com/bep/gowebp="v0.1.0"
|
||||
github.com/bep/overlayfs="v0.6.0"
|
||||
github.com/bep/tmc="v0.5.1"
|
||||
github.com/clbanning/mxj/v2="v2.5.5"
|
||||
github.com/cli/safeexec="v1.0.0"
|
||||
github.com/cpuguy83/go-md2man/v2="v2.0.0"
|
||||
github.com/cpuguy83/go-md2man/v2="v2.0.1"
|
||||
github.com/disintegration/gift="v1.2.1"
|
||||
github.com/dlclark/regexp2="v1.4.0"
|
||||
github.com/dustin/go-humanize="v1.0.0"
|
||||
github.com/evanw/esbuild="v0.13.12"
|
||||
github.com/fsnotify/fsnotify="v1.5.1"
|
||||
github.com/getkin/kin-openapi="v0.80.0"
|
||||
github.com/evanw/esbuild="v0.14.42"
|
||||
github.com/frankban/quicktest="v1.14.3"
|
||||
github.com/fsnotify/fsnotify="v1.5.4"
|
||||
github.com/getkin/kin-openapi="v0.94.0"
|
||||
github.com/ghodss/yaml="v1.0.0"
|
||||
github.com/go-openapi/jsonpointer="v0.19.5"
|
||||
github.com/go-openapi/swag="v0.19.5"
|
||||
github.com/gobuffalo/flect="v0.2.3"
|
||||
github.com/gobuffalo/flect="v0.2.5"
|
||||
github.com/gobwas/glob="v0.2.3"
|
||||
github.com/gohugoio/go-i18n/v2="v2.1.3-0.20210430103248-4c28c89f8013"
|
||||
github.com/gohugoio/locales="v0.14.0"
|
||||
github.com/gohugoio/localescompressed="v0.14.0"
|
||||
github.com/golang/groupcache="v0.0.0-20200121045136-8c9f03a8e57e"
|
||||
github.com/gohugoio/localescompressed="v1.0.1"
|
||||
github.com/golang-jwt/jwt/v4="v4.0.0"
|
||||
github.com/golang/groupcache="v0.0.0-20210331224755-41bb18bfe9da"
|
||||
github.com/golang/protobuf="v1.5.2"
|
||||
github.com/google/go-cmp="v0.5.6"
|
||||
github.com/google/uuid="v1.1.2"
|
||||
github.com/google/wire="v0.4.0"
|
||||
github.com/googleapis/gax-go/v2="v2.0.5"
|
||||
github.com/googleapis/gax-go="v2.0.2+incompatible"
|
||||
github.com/gorilla/websocket="v1.4.2"
|
||||
github.com/google/go-cmp="v0.5.8"
|
||||
github.com/google/uuid="v1.3.0"
|
||||
github.com/google/wire="v0.5.0"
|
||||
github.com/googleapis/gax-go/v2="v2.3.0"
|
||||
github.com/googleapis/go-type-adapters="v1.0.0"
|
||||
github.com/gorilla/websocket="v1.5.0"
|
||||
github.com/hairyhenderson/go-codeowners="v0.2.3-0.20201026200250-cdc7c0759690"
|
||||
github.com/inconshreveable/mousetrap="v1.0.0"
|
||||
github.com/jdkato/prose="v1.2.1"
|
||||
github.com/jmespath/go-jmespath="v0.4.0"
|
||||
github.com/kyokomi/emoji/v2="v2.2.8"
|
||||
github.com/kr/pretty="v0.3.0"
|
||||
github.com/kr/text="v0.2.0"
|
||||
github.com/kyokomi/emoji/v2="v2.2.9"
|
||||
github.com/mailru/easyjson="v0.0.0-20190626092158-b2ccc519800e"
|
||||
github.com/mattn/go-ieproxy="v0.0.1"
|
||||
github.com/mattn/go-isatty="v0.0.14"
|
||||
github.com/mattn/go-runewidth="v0.0.9"
|
||||
github.com/miekg/mmark="v1.3.6"
|
||||
github.com/mitchellh/hashstructure="v1.1.0"
|
||||
github.com/mitchellh/mapstructure="v1.4.2"
|
||||
github.com/mitchellh/mapstructure="v1.5.0"
|
||||
github.com/muesli/smartcrop="v0.3.0"
|
||||
github.com/niklasfasching/go-org="v1.5.0"
|
||||
github.com/niklasfasching/go-org="v1.6.2"
|
||||
github.com/olekukonko/tablewriter="v0.0.5"
|
||||
github.com/pelletier/go-toml/v2="v2.0.0-beta.3.0.20210727221244-fa0796069526"
|
||||
github.com/pkg/errors="v0.9.1"
|
||||
github.com/rogpeppe/go-internal="v1.8.0"
|
||||
github.com/russross/blackfriday/v2="v2.0.1"
|
||||
github.com/russross/blackfriday="v1.5.3-0.20200218234912-41c5fccfd6f6"
|
||||
github.com/pelletier/go-toml/v2="v2.0.0-beta.7.0.20220408132554-2377ac4bc04c"
|
||||
github.com/rogpeppe/go-internal="v1.8.1"
|
||||
github.com/russross/blackfriday/v2="v2.1.0"
|
||||
github.com/rwcarlsen/goexif="v0.0.0-20190401172101-9e8deecbddbd"
|
||||
github.com/sanity-io/litter="v1.5.1"
|
||||
github.com/sanity-io/litter="v1.5.5"
|
||||
github.com/sass/libsass="3.6.5"
|
||||
github.com/shurcooL/sanitized_anchor_name="v1.0.0"
|
||||
github.com/spf13/afero="v1.6.0"
|
||||
github.com/spf13/cast="v1.4.1"
|
||||
github.com/spf13/cobra="v1.2.1"
|
||||
github.com/spf13/afero="v1.8.2"
|
||||
github.com/spf13/cast="v1.5.0"
|
||||
github.com/spf13/cobra="v1.4.0"
|
||||
github.com/spf13/fsync="v0.9.0"
|
||||
github.com/spf13/jwalterweatherman="v1.1.0"
|
||||
github.com/spf13/pflag="v1.0.5"
|
||||
github.com/tdewolff/minify/v2="v2.9.22"
|
||||
github.com/tdewolff/parse/v2="v2.5.21"
|
||||
github.com/tdewolff/minify/v2="v2.11.5"
|
||||
github.com/tdewolff/parse/v2="v2.5.31"
|
||||
github.com/webmproject/libwebp="v1.2.0"
|
||||
github.com/yuin/goldmark-highlighting="v0.0.0-20200307114337-60d527fdb691"
|
||||
github.com/yuin/goldmark="v1.4.2"
|
||||
github.com/yuin/goldmark="v1.4.12"
|
||||
go.opencensus.io="v0.23.0"
|
||||
gocloud.dev="v0.20.0"
|
||||
golang.org/x/image="v0.0.0-20210220032944-ac19c3e999fb"
|
||||
golang.org/x/net="v0.0.0-20210614182718-04defd469f4e"
|
||||
golang.org/x/oauth2="v0.0.0-20210628180205-a41e5a781914"
|
||||
go.uber.org/atomic="v1.9.0"
|
||||
gocloud.dev="v0.24.0"
|
||||
golang.org/x/crypto="v0.0.0-20211108221036-ceb1ce70b4fa"
|
||||
golang.org/x/image="v0.0.0-20211028202545-6944b10bf410"
|
||||
golang.org/x/net="v0.0.0-20220425223048-2871e0cb64e4"
|
||||
golang.org/x/oauth2="v0.0.0-20220411215720-9780585627b5"
|
||||
golang.org/x/sync="v0.0.0-20210220032951-036812b2e83c"
|
||||
golang.org/x/sys="v0.0.0-20210908233432-aa78b53d3365"
|
||||
golang.org/x/sys="v0.0.0-20220422013727-9388b58f7150"
|
||||
golang.org/x/text="v0.3.7"
|
||||
golang.org/x/xerrors="v0.0.0-20200804184101-5ec99f83aff1"
|
||||
google.golang.org/api="v0.51.0"
|
||||
google.golang.org/genproto="v0.0.0-20210716133855-ce7ef5c701ea"
|
||||
google.golang.org/grpc="v1.39.0"
|
||||
google.golang.org/protobuf="v1.27.1"
|
||||
golang.org/x/tools="v0.1.10"
|
||||
golang.org/x/xerrors="v0.0.0-20220411194840-2f41105eb62f"
|
||||
google.golang.org/api="v0.76.0"
|
||||
google.golang.org/genproto="v0.0.0-20220426171045-31bebdecfb46"
|
||||
google.golang.org/grpc="v1.46.0"
|
||||
google.golang.org/protobuf="v1.28.0"
|
||||
gopkg.in/yaml.v2="v2.4.0"
|
||||
```
|
||||
|
||||
|
||||
@@ -369,12 +369,13 @@ func (c *commandeer) loadConfig() error {
|
||||
c.configFiles = configFiles
|
||||
|
||||
var ok bool
|
||||
loc := time.Local
|
||||
c.languages, ok = c.Cfg.Get("languagesSorted").(langs.Languages)
|
||||
if !ok {
|
||||
panic("languages not configured")
|
||||
if ok {
|
||||
loc = langs.GetLocation(c.languages[0])
|
||||
}
|
||||
|
||||
err = c.initClock(langs.GetLocation(c.languages[0]))
|
||||
err = c.initClock(loc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -305,6 +305,7 @@ func (cc *hugoBuilderCommon) handleFlags(cmd *cobra.Command) {
|
||||
cmd.Flags().BoolP("forceSyncStatic", "", false, "copy all files when static is changed.")
|
||||
cmd.Flags().BoolP("noTimes", "", false, "don't sync modification time of files")
|
||||
cmd.Flags().BoolP("noChmod", "", false, "don't sync permission mode of files")
|
||||
cmd.Flags().BoolP("noBuildLock", "", false, "don't create .hugo_build.lock file")
|
||||
cmd.Flags().BoolP("printI18nWarnings", "", false, "print missing translations")
|
||||
cmd.Flags().BoolP("printPathWarnings", "", false, "print warnings on duplicate target paths etc.")
|
||||
cmd.Flags().BoolP("printUnusedTemplates", "", false, "print warnings on unused templates.")
|
||||
|
||||
@@ -16,9 +16,9 @@ package commands
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/alecthomas/chroma"
|
||||
"github.com/alecthomas/chroma/formatters/html"
|
||||
"github.com/alecthomas/chroma/styles"
|
||||
"github.com/alecthomas/chroma/v2"
|
||||
"github.com/alecthomas/chroma/v2/formatters/html"
|
||||
"github.com/alecthomas/chroma/v2/styles"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
||||
+13
-5
@@ -127,6 +127,12 @@ func initializeConfig(mustHaveConfigFile, failOnInitErr, running bool,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if h := c.hugoTry(); h != nil {
|
||||
for _, s := range h.Sites {
|
||||
s.RegisterMediaTypes()
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
@@ -200,6 +206,7 @@ func initializeFlags(cmd *cobra.Command, cfg config.Provider) {
|
||||
"forceSyncStatic",
|
||||
"noTimes",
|
||||
"noChmod",
|
||||
"noBuildLock",
|
||||
"ignoreVendorPaths",
|
||||
"templateMetrics",
|
||||
"templateMetricsHints",
|
||||
@@ -277,10 +284,6 @@ func setValueFromFlag(flags *flag.FlagSet, key string, cfg config.Provider, targ
|
||||
}
|
||||
}
|
||||
|
||||
func isTerminal() bool {
|
||||
return terminal.IsTerminal(os.Stdout)
|
||||
}
|
||||
|
||||
func (c *commandeer) fullBuild(noBuildLock bool) error {
|
||||
var (
|
||||
g errgroup.Group
|
||||
@@ -290,7 +293,7 @@ func (c *commandeer) fullBuild(noBuildLock bool) error {
|
||||
if !c.h.quiet {
|
||||
fmt.Println("Start building sites … ")
|
||||
fmt.Println(hugo.BuildVersionString())
|
||||
if isTerminal() {
|
||||
if terminal.IsTerminal(os.Stdout) {
|
||||
defer func() {
|
||||
fmt.Print(showCursor + clearLine)
|
||||
}()
|
||||
@@ -860,8 +863,13 @@ func (c *commandeer) newWatcher(pollIntervalStr string, dirList ...string) (*wat
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spec := c.hugo().Deps.SourceSpec
|
||||
|
||||
for _, d := range dirList {
|
||||
if d != "" {
|
||||
if spec.IgnoreFile(d) {
|
||||
continue
|
||||
}
|
||||
_ = watcher.Add(d)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ import (
|
||||
"github.com/gohugoio/hugo/common/maps"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/hugolib"
|
||||
"github.com/gohugoio/hugo/parser"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -202,12 +201,7 @@ func (i *importCmd) retrieveJekyllPostDir(fs afero.Fs, dir string) (bool, bool)
|
||||
}
|
||||
|
||||
func (i *importCmd) createSiteFromJekyll(jekyllRoot, targetDir string, jekyllPostDirs map[string]bool, force bool) error {
|
||||
s, err := hugolib.NewSiteDefaultLang()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fs := s.Fs.Source
|
||||
fs := &afero.OsFs{}
|
||||
if exists, _ := helpers.Exists(targetDir, fs); exists {
|
||||
if isDir, _ := helpers.IsDir(targetDir, fs); !isDir {
|
||||
return errors.New("target path \"" + targetDir + "\" exists but is not a directory")
|
||||
|
||||
+8
-4
@@ -168,6 +168,14 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
|
||||
c.Set("watch", true)
|
||||
}
|
||||
|
||||
// TODO(bep) see issue 9901
|
||||
// cfgInit is called twice, before and after the languages have been initialized.
|
||||
// The servers (below) can not be initialized before we
|
||||
// know if we're configured in a multihost setup.
|
||||
if len(c.languages) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We can only do this once.
|
||||
serverCfgInit.Do(func() {
|
||||
c.serverPorts = make([]serverPortListener, 1)
|
||||
@@ -264,10 +272,6 @@ func (sc *serverCmd) server(cmd *cobra.Command, args []string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, s := range c.hugo().Sites {
|
||||
s.RegisterMediaTypes()
|
||||
}
|
||||
|
||||
// Watch runs its own server as part of the routine
|
||||
if sc.serverWatch {
|
||||
|
||||
|
||||
+48
-21
@@ -41,7 +41,7 @@ func TestServerPanicOnConfigError(t *testing.T) {
|
||||
linenos='table'
|
||||
`
|
||||
|
||||
r := runServerTest(c, false, config)
|
||||
r := runServerTest(c, 0, config)
|
||||
|
||||
c.Assert(r.err, qt.IsNotNil)
|
||||
c.Assert(r.err.Error(), qt.Contains, "cannot parse 'Highlight.LineNos' as bool:")
|
||||
@@ -52,7 +52,7 @@ func TestServerFlags(t *testing.T) {
|
||||
|
||||
assertPublic := func(c *qt.C, r serverTestResult, renderStaticToDisk bool) {
|
||||
c.Assert(r.err, qt.IsNil)
|
||||
c.Assert(r.homeContent, qt.Contains, "Environment: development")
|
||||
c.Assert(r.homesContent[0], qt.Contains, "Environment: development")
|
||||
c.Assert(r.publicDirnames["myfile.txt"], qt.Equals, renderStaticToDisk)
|
||||
|
||||
}
|
||||
@@ -81,7 +81,7 @@ baseURL="https://example.org"
|
||||
args = strings.Split(test.flag, "=")
|
||||
}
|
||||
|
||||
r := runServerTest(c, true, config, args...)
|
||||
r := runServerTest(c, 1, config, args...)
|
||||
|
||||
test.assert(c, r)
|
||||
|
||||
@@ -95,30 +95,52 @@ func TestServerBugs(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
flag string
|
||||
assert func(c *qt.C, r serverTestResult)
|
||||
name string
|
||||
config string
|
||||
flag string
|
||||
numservers int
|
||||
assert func(c *qt.C, r serverTestResult)
|
||||
}{
|
||||
// Issue 9788
|
||||
{"PostProcess, memory", "", func(c *qt.C, r serverTestResult) {
|
||||
{"PostProcess, memory", "", "", 1, func(c *qt.C, r serverTestResult) {
|
||||
c.Assert(r.err, qt.IsNil)
|
||||
c.Assert(r.homeContent, qt.Contains, "PostProcess: /foo.min.css")
|
||||
c.Assert(r.homesContent[0], qt.Contains, "PostProcess: /foo.min.css")
|
||||
}},
|
||||
{"PostProcess, disk", "--renderToDisk", func(c *qt.C, r serverTestResult) {
|
||||
{"PostProcess, disk", "", "--renderToDisk", 1, func(c *qt.C, r serverTestResult) {
|
||||
c.Assert(r.err, qt.IsNil)
|
||||
c.Assert(r.homeContent, qt.Contains, "PostProcess: /foo.min.css")
|
||||
c.Assert(r.homesContent[0], qt.Contains, "PostProcess: /foo.min.css")
|
||||
}},
|
||||
// Isue 9901
|
||||
{"Multihost", `
|
||||
defaultContentLanguage = 'en'
|
||||
[languages]
|
||||
[languages.en]
|
||||
baseURL = 'https://example.com'
|
||||
title = 'My blog'
|
||||
weight = 1
|
||||
[languages.fr]
|
||||
baseURL = 'https://example.fr'
|
||||
title = 'Mon blogue'
|
||||
weight = 2
|
||||
`, "", 2, func(c *qt.C, r serverTestResult) {
|
||||
c.Assert(r.err, qt.IsNil)
|
||||
for i, s := range []string{"My blog", "Mon blogue"} {
|
||||
c.Assert(r.homesContent[i], qt.Contains, s)
|
||||
}
|
||||
}},
|
||||
} {
|
||||
c.Run(test.name, func(c *qt.C) {
|
||||
config := `
|
||||
if test.config == "" {
|
||||
test.config = `
|
||||
baseURL="https://example.org"
|
||||
`
|
||||
}
|
||||
|
||||
var args []string
|
||||
if test.flag != "" {
|
||||
args = strings.Split(test.flag, "=")
|
||||
}
|
||||
r := runServerTest(c, true, config, args...)
|
||||
r := runServerTest(c, test.numservers, test.config, args...)
|
||||
test.assert(c, r)
|
||||
|
||||
})
|
||||
@@ -129,11 +151,11 @@ baseURL="https://example.org"
|
||||
|
||||
type serverTestResult struct {
|
||||
err error
|
||||
homeContent string
|
||||
homesContent []string
|
||||
publicDirnames map[string]bool
|
||||
}
|
||||
|
||||
func runServerTest(c *qt.C, getHome bool, config string, args ...string) (result serverTestResult) {
|
||||
func runServerTest(c *qt.C, getNumHomes int, config string, args ...string) (result serverTestResult) {
|
||||
dir := createSimpleTestSite(c, testSiteConfig{configTOML: config})
|
||||
|
||||
sp, err := helpers.FindAvailablePort()
|
||||
@@ -162,16 +184,21 @@ func runServerTest(c *qt.C, getHome bool, config string, args ...string) (result
|
||||
return err
|
||||
})
|
||||
|
||||
if getHome {
|
||||
if getNumHomes > 0 {
|
||||
// Esp. on slow CI machines, we need to wait a little before the web
|
||||
// server is ready.
|
||||
time.Sleep(567 * time.Millisecond)
|
||||
resp, err := http.Get(fmt.Sprintf("http://localhost:%d/", port))
|
||||
c.Check(err, qt.IsNil)
|
||||
c.Check(resp.StatusCode, qt.Equals, http.StatusOK)
|
||||
if err == nil {
|
||||
defer resp.Body.Close()
|
||||
result.homeContent = helpers.ReaderToString(resp.Body)
|
||||
result.homesContent = make([]string, getNumHomes)
|
||||
for i := 0; i < getNumHomes; i++ {
|
||||
func() {
|
||||
resp, err := http.Get(fmt.Sprintf("http://localhost:%d/", port+i))
|
||||
c.Check(err, qt.IsNil)
|
||||
c.Check(resp.StatusCode, qt.Equals, http.StatusOK)
|
||||
if err == nil {
|
||||
defer resp.Body.Close()
|
||||
result.homesContent[i] = helpers.ReaderToString(resp.Body)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,9 @@ import (
|
||||
"context"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gohugoio/hugo/common/htime"
|
||||
"github.com/gohugoio/hugo/common/types"
|
||||
)
|
||||
|
||||
@@ -168,6 +170,44 @@ func GetMethodIndexByName(tp reflect.Type, name string) int {
|
||||
return m.Index
|
||||
}
|
||||
|
||||
var (
|
||||
timeType = reflect.TypeOf((*time.Time)(nil)).Elem()
|
||||
asTimeProviderType = reflect.TypeOf((*htime.AsTimeProvider)(nil)).Elem()
|
||||
)
|
||||
|
||||
// IsTime returns whether tp is a time.Time type or if it can be converted into one
|
||||
// in ToTime.
|
||||
func IsTime(tp reflect.Type) bool {
|
||||
if tp == timeType {
|
||||
return true
|
||||
}
|
||||
|
||||
if tp.Implements(asTimeProviderType) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AsTime returns v as a time.Time if possible.
|
||||
// The given location is only used if the value implements AsTimeProvider (e.g. go-toml local).
|
||||
// A zero Time and false is returned if this isn't possible.
|
||||
// Note that this function does not accept string dates.
|
||||
func AsTime(v reflect.Value, loc *time.Location) (time.Time, bool) {
|
||||
if v.Kind() == reflect.Interface {
|
||||
return AsTime(v.Elem(), loc)
|
||||
}
|
||||
|
||||
if v.Type() == timeType {
|
||||
return v.Interface().(time.Time), true
|
||||
}
|
||||
|
||||
if v.Type().Implements(asTimeProviderType) {
|
||||
return v.Interface().(htime.AsTimeProvider).AsTime(loc), true
|
||||
}
|
||||
|
||||
return time.Time{}, false
|
||||
}
|
||||
|
||||
// Based on: https://github.com/golang/go/blob/178a2c42254166cffed1b25fb1d3c7a5727cada6/src/text/template/exec.go#L931
|
||||
func indirectInterface(v reflect.Value) reflect.Value {
|
||||
if v.Kind() != reflect.Interface {
|
||||
|
||||
@@ -20,8 +20,6 @@ import (
|
||||
"github.com/bep/clock"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
toml "github.com/pelletier/go-toml/v2"
|
||||
|
||||
"github.com/gohugoio/locales"
|
||||
)
|
||||
|
||||
@@ -139,13 +137,12 @@ func (f TimeFormatter) Format(t time.Time, layout string) string {
|
||||
|
||||
func ToTimeInDefaultLocationE(i any, location *time.Location) (tim time.Time, err error) {
|
||||
switch vv := i.(type) {
|
||||
case toml.LocalDate:
|
||||
return vv.AsTime(location), nil
|
||||
case toml.LocalDateTime:
|
||||
case AsTimeProvider:
|
||||
return vv.AsTime(location), nil
|
||||
// issue #8895
|
||||
// datetimes parsed by `go-toml` have empty zone name
|
||||
// convert back them into string and use `cast`
|
||||
// TODO(bep) add tests, make sure we really need this.
|
||||
case time.Time:
|
||||
i = vv.Format(time.RFC3339)
|
||||
}
|
||||
@@ -161,3 +158,8 @@ func Now() time.Time {
|
||||
func Since(t time.Time) time.Duration {
|
||||
return Clock.Since(t)
|
||||
}
|
||||
|
||||
// AsTimeProvider is implemented by go-toml's LocalDate and LocalDateTime.
|
||||
type AsTimeProvider interface {
|
||||
AsTime(zone *time.Location) time.Time
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// buildDate allows vendor-specified build date when .git/ is unavailable.
|
||||
buildDate string
|
||||
// vendorInfo contains vendor notes about the current build.
|
||||
vendorInfo string
|
||||
)
|
||||
@@ -53,6 +55,9 @@ type Info struct {
|
||||
// It can be any string, but it will be all lower case.
|
||||
Environment string
|
||||
|
||||
// version of go that the Hugo binary was built with
|
||||
GoVersion string
|
||||
|
||||
deps []*Dependency
|
||||
}
|
||||
|
||||
@@ -87,12 +92,14 @@ func NewInfo(environment string, deps []*Dependency) Info {
|
||||
var (
|
||||
commitHash string
|
||||
buildDate string
|
||||
goVersion string
|
||||
)
|
||||
|
||||
bi := getBuildInfo()
|
||||
if bi != nil {
|
||||
commitHash = bi.Revision
|
||||
buildDate = bi.RevisionTime
|
||||
goVersion = bi.GoVersion
|
||||
}
|
||||
|
||||
return Info{
|
||||
@@ -100,6 +107,7 @@ func NewInfo(environment string, deps []*Dependency) Info {
|
||||
BuildDate: buildDate,
|
||||
Environment: environment,
|
||||
deps: deps,
|
||||
GoVersion: goVersion,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ func TestHugoInfo(t *testing.T) {
|
||||
if bi != nil {
|
||||
c.Assert(hugoInfo.CommitHash, qt.Equals, bi.Revision)
|
||||
c.Assert(hugoInfo.BuildDate, qt.Equals, bi.RevisionTime)
|
||||
c.Assert(hugoInfo.GoVersion, qt.Equals, bi.GoVersion)
|
||||
}
|
||||
c.Assert(hugoInfo.Environment, qt.Equals, "production")
|
||||
c.Assert(string(hugoInfo.Generator()), qt.Contains, fmt.Sprintf("Hugo %s", hugoInfo.Version()))
|
||||
|
||||
+87
-56
@@ -16,7 +16,9 @@ package hugo
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gohugoio/hugo/compare"
|
||||
@@ -25,8 +27,9 @@ import (
|
||||
|
||||
// Version represents the Hugo build version.
|
||||
type Version struct {
|
||||
// Major and minor version.
|
||||
Number float32
|
||||
Major int
|
||||
|
||||
Minor int
|
||||
|
||||
// Increment this for bug releases
|
||||
PatchLevel int
|
||||
@@ -42,7 +45,7 @@ var (
|
||||
)
|
||||
|
||||
func (v Version) String() string {
|
||||
return version(v.Number, v.PatchLevel, v.Suffix)
|
||||
return version(v.Major, v.Minor, v.PatchLevel, v.Suffix)
|
||||
}
|
||||
|
||||
// Version returns the Hugo version.
|
||||
@@ -50,6 +53,11 @@ func (v Version) Version() VersionString {
|
||||
return VersionString(v.String())
|
||||
}
|
||||
|
||||
// Compare implements the compare.Comparer interface.
|
||||
func (h Version) Compare(other any) int {
|
||||
return compareVersions(h, other)
|
||||
}
|
||||
|
||||
// VersionString represents a Hugo version string.
|
||||
type VersionString string
|
||||
|
||||
@@ -60,7 +68,7 @@ func (h VersionString) String() string {
|
||||
// Compare implements the compare.Comparer interface.
|
||||
func (h VersionString) Compare(other any) int {
|
||||
v := MustParseVersion(h.String())
|
||||
return compareVersionsWithSuffix(v.Number, v.PatchLevel, v.Suffix, other)
|
||||
return compareVersions(v, other)
|
||||
}
|
||||
|
||||
// Eq implements the compare.Eqer interface.
|
||||
@@ -84,10 +92,7 @@ func ParseVersion(s string) (Version, error) {
|
||||
}
|
||||
}
|
||||
|
||||
v, p := parseVersion(s)
|
||||
|
||||
vv.Number = v
|
||||
vv.PatchLevel = p
|
||||
vv.Major, vv.Minor, vv.PatchLevel = parseVersion(s)
|
||||
|
||||
return vv, nil
|
||||
}
|
||||
@@ -110,18 +115,20 @@ func (v Version) ReleaseVersion() Version {
|
||||
|
||||
// Next returns the next Hugo release version.
|
||||
func (v Version) Next() Version {
|
||||
return Version{Number: v.Number + 0.01}
|
||||
return Version{Major: v.Major, Minor: v.Minor + 1}
|
||||
}
|
||||
|
||||
// Prev returns the previous Hugo release version.
|
||||
func (v Version) Prev() Version {
|
||||
return Version{Number: v.Number - 0.01}
|
||||
return Version{Major: v.Major, Minor: v.Minor - 1}
|
||||
}
|
||||
|
||||
// NextPatchLevel returns the next patch/bugfix Hugo version.
|
||||
// This will be a patch increment on the previous Hugo version.
|
||||
func (v Version) NextPatchLevel(level int) Version {
|
||||
return Version{Number: v.Number - 0.01, PatchLevel: level}
|
||||
prev := v.Prev()
|
||||
prev.PatchLevel = level
|
||||
return prev
|
||||
}
|
||||
|
||||
// BuildVersionString creates a version string. This is what you see when
|
||||
@@ -146,6 +153,10 @@ func BuildVersionString() string {
|
||||
osArch := bi.GoOS + "/" + bi.GoArch
|
||||
|
||||
date := bi.RevisionTime
|
||||
if date == "" {
|
||||
// Accept vendor-specified build date if .git/ is unavailable.
|
||||
date = buildDate
|
||||
}
|
||||
if date == "" {
|
||||
date = "unknown"
|
||||
}
|
||||
@@ -160,11 +171,11 @@ func BuildVersionString() string {
|
||||
return versionString
|
||||
}
|
||||
|
||||
func version(version float32, patchVersion int, suffix string) string {
|
||||
if patchVersion > 0 || version > 0.53 {
|
||||
return fmt.Sprintf("%.2f.%d%s", version, patchVersion, suffix)
|
||||
func version(major, minor, patch int, suffix string) string {
|
||||
if patch > 0 || minor > 53 {
|
||||
return fmt.Sprintf("%d.%d.%d%s", major, minor, patch, suffix)
|
||||
}
|
||||
return fmt.Sprintf("%.2f%s", version, suffix)
|
||||
return fmt.Sprintf("%d.%d%s", major, minor, suffix)
|
||||
}
|
||||
|
||||
// CompareVersion compares the given version string or number against the
|
||||
@@ -172,26 +183,41 @@ func version(version float32, patchVersion int, suffix string) string {
|
||||
// It returns -1 if the given version is less than, 0 if equal and 1 if greater than
|
||||
// the running version.
|
||||
func CompareVersion(version any) int {
|
||||
return compareVersionsWithSuffix(CurrentVersion.Number, CurrentVersion.PatchLevel, CurrentVersion.Suffix, version)
|
||||
return compareVersions(CurrentVersion, version)
|
||||
}
|
||||
|
||||
func compareVersions(inVersion float32, inPatchVersion int, in any) int {
|
||||
return compareVersionsWithSuffix(inVersion, inPatchVersion, "", in)
|
||||
}
|
||||
|
||||
func compareVersionsWithSuffix(inVersion float32, inPatchVersion int, suffix string, in any) int {
|
||||
func compareVersions(inVersion Version, in any) int {
|
||||
var c int
|
||||
switch d := in.(type) {
|
||||
case float64:
|
||||
c = compareFloatVersions(inVersion, float32(d))
|
||||
c = compareFloatWithVersion(d, inVersion)
|
||||
case float32:
|
||||
c = compareFloatVersions(inVersion, d)
|
||||
c = compareFloatWithVersion(float64(d), inVersion)
|
||||
case int:
|
||||
c = compareFloatVersions(inVersion, float32(d))
|
||||
c = compareFloatWithVersion(float64(d), inVersion)
|
||||
case int32:
|
||||
c = compareFloatVersions(inVersion, float32(d))
|
||||
c = compareFloatWithVersion(float64(d), inVersion)
|
||||
case int64:
|
||||
c = compareFloatVersions(inVersion, float32(d))
|
||||
c = compareFloatWithVersion(float64(d), inVersion)
|
||||
case Version:
|
||||
if d.Major == inVersion.Major && d.Minor == inVersion.Minor && d.PatchLevel == inVersion.PatchLevel {
|
||||
return strings.Compare(inVersion.Suffix, d.Suffix)
|
||||
}
|
||||
if d.Major > inVersion.Major {
|
||||
return 1
|
||||
} else if d.Major < inVersion.Major {
|
||||
return -1
|
||||
}
|
||||
if d.Minor > inVersion.Minor {
|
||||
return 1
|
||||
} else if d.Minor < inVersion.Minor {
|
||||
return -1
|
||||
}
|
||||
if d.PatchLevel > inVersion.PatchLevel {
|
||||
return 1
|
||||
} else if d.PatchLevel < inVersion.PatchLevel {
|
||||
return -1
|
||||
}
|
||||
default:
|
||||
s, err := cast.ToStringE(in)
|
||||
if err != nil {
|
||||
@@ -202,50 +228,55 @@ func compareVersionsWithSuffix(inVersion float32, inPatchVersion int, suffix str
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return inVersion.Compare(v)
|
||||
|
||||
if v.Number == inVersion && v.PatchLevel == inPatchVersion {
|
||||
return strings.Compare(suffix, v.Suffix)
|
||||
}
|
||||
|
||||
if v.Number < inVersion || (v.Number == inVersion && v.PatchLevel < inPatchVersion) {
|
||||
return -1
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
if c == 0 && suffix != "" {
|
||||
return 1
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func parseVersion(s string) (float32, int) {
|
||||
var (
|
||||
v float32
|
||||
p int
|
||||
)
|
||||
|
||||
if strings.Count(s, ".") == 2 {
|
||||
li := strings.LastIndex(s, ".")
|
||||
p = cast.ToInt(s[li+1:])
|
||||
s = s[:li]
|
||||
func parseVersion(s string) (int, int, int) {
|
||||
var major, minor, patch int
|
||||
parts := strings.Split(s, ".")
|
||||
if len(parts) > 0 {
|
||||
major, _ = strconv.Atoi(parts[0])
|
||||
}
|
||||
if len(parts) > 1 {
|
||||
minor, _ = strconv.Atoi(parts[1])
|
||||
}
|
||||
if len(parts) > 2 {
|
||||
patch, _ = strconv.Atoi(parts[2])
|
||||
}
|
||||
|
||||
v = float32(cast.ToFloat64(s))
|
||||
|
||||
return v, p
|
||||
return major, minor, patch
|
||||
}
|
||||
|
||||
func compareFloatVersions(version float32, v float32) int {
|
||||
if v == version {
|
||||
// compareFloatWithVersion compares v1 with v2.
|
||||
// It returns -1 if v1 is less than v2, 0 if v1 is equal to v2 and 1 if v1 is greater than v2.
|
||||
func compareFloatWithVersion(v1 float64, v2 Version) int {
|
||||
mf, minf := math.Modf(v1)
|
||||
v1maj := int(mf)
|
||||
v1min := int(minf * 100)
|
||||
|
||||
if v2.Major == v1maj && v2.Minor == v1min {
|
||||
return 0
|
||||
}
|
||||
if v < version {
|
||||
|
||||
if v1maj > v2.Major {
|
||||
return 1
|
||||
|
||||
}
|
||||
|
||||
if v1maj < v2.Major {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
|
||||
if v1min > v2.Minor {
|
||||
return 1
|
||||
}
|
||||
|
||||
return -1
|
||||
|
||||
}
|
||||
|
||||
func GoMinorVersion() int {
|
||||
|
||||
@@ -16,7 +16,8 @@ package hugo
|
||||
// CurrentVersion represents the current build version.
|
||||
// This should be the only one.
|
||||
var CurrentVersion = Version{
|
||||
Number: 0.99,
|
||||
Major: 0,
|
||||
Minor: 102,
|
||||
PatchLevel: 0,
|
||||
Suffix: "",
|
||||
Suffix: "-DEV",
|
||||
}
|
||||
|
||||
+24
-25
@@ -22,10 +22,10 @@ import (
|
||||
func TestHugoVersion(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(version(0.15, 0, "-DEV"), qt.Equals, "0.15-DEV")
|
||||
c.Assert(version(0.15, 2, "-DEV"), qt.Equals, "0.15.2-DEV")
|
||||
c.Assert(version(0, 15, 0, "-DEV"), qt.Equals, "0.15-DEV")
|
||||
c.Assert(version(0, 15, 2, "-DEV"), qt.Equals, "0.15.2-DEV")
|
||||
|
||||
v := Version{Number: 0.21, PatchLevel: 0, Suffix: "-DEV"}
|
||||
v := Version{Minor: 21, Suffix: "-DEV"}
|
||||
|
||||
c.Assert(v.ReleaseVersion().String(), qt.Equals, "0.21")
|
||||
c.Assert(v.String(), qt.Equals, "0.21-DEV")
|
||||
@@ -39,37 +39,36 @@ func TestHugoVersion(t *testing.T) {
|
||||
|
||||
// We started to use full semver versions even for main
|
||||
// releases in v0.54.0
|
||||
v = Version{Number: 0.53, PatchLevel: 0}
|
||||
v = Version{Minor: 53, PatchLevel: 0}
|
||||
c.Assert(v.String(), qt.Equals, "0.53")
|
||||
c.Assert(v.Next().String(), qt.Equals, "0.54.0")
|
||||
c.Assert(v.Next().Next().String(), qt.Equals, "0.55.0")
|
||||
v = Version{Number: 0.54, PatchLevel: 0, Suffix: "-DEV"}
|
||||
v = Version{Minor: 54, PatchLevel: 0, Suffix: "-DEV"}
|
||||
c.Assert(v.String(), qt.Equals, "0.54.0-DEV")
|
||||
}
|
||||
|
||||
func TestCompareVersions(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(compareVersions(0.20, 0, 0.20), qt.Equals, 0)
|
||||
c.Assert(compareVersions(0.20, 0, float32(0.20)), qt.Equals, 0)
|
||||
c.Assert(compareVersions(0.20, 0, float64(0.20)), qt.Equals, 0)
|
||||
c.Assert(compareVersions(0.19, 1, 0.20), qt.Equals, 1)
|
||||
c.Assert(compareVersions(0.19, 3, "0.20.2"), qt.Equals, 1)
|
||||
c.Assert(compareVersions(0.19, 1, 0.01), qt.Equals, -1)
|
||||
c.Assert(compareVersions(0, 1, 3), qt.Equals, 1)
|
||||
c.Assert(compareVersions(0, 1, int32(3)), qt.Equals, 1)
|
||||
c.Assert(compareVersions(0, 1, int64(3)), qt.Equals, 1)
|
||||
c.Assert(compareVersions(0.20, 0, "0.20"), qt.Equals, 0)
|
||||
c.Assert(compareVersions(0.20, 1, "0.20.1"), qt.Equals, 0)
|
||||
c.Assert(compareVersions(0.20, 1, "0.20"), qt.Equals, -1)
|
||||
c.Assert(compareVersions(0.20, 0, "0.20.1"), qt.Equals, 1)
|
||||
c.Assert(compareVersions(0.20, 1, "0.20.2"), qt.Equals, 1)
|
||||
c.Assert(compareVersions(0.21, 1, "0.22.1"), qt.Equals, 1)
|
||||
c.Assert(compareVersions(0.22, 0, "0.22-DEV"), qt.Equals, -1)
|
||||
c.Assert(compareVersions(0.22, 0, "0.22.1-DEV"), qt.Equals, 1)
|
||||
c.Assert(compareVersionsWithSuffix(0.22, 0, "-DEV", "0.22"), qt.Equals, 1)
|
||||
c.Assert(compareVersionsWithSuffix(0.22, 1, "-DEV", "0.22"), qt.Equals, -1)
|
||||
c.Assert(compareVersionsWithSuffix(0.22, 1, "-DEV", "0.22.1-DEV"), qt.Equals, 0)
|
||||
c.Assert(compareVersions(MustParseVersion("0.20.0"), 0.20), qt.Equals, 0)
|
||||
c.Assert(compareVersions(MustParseVersion("0.20.0"), float32(0.20)), qt.Equals, 0)
|
||||
c.Assert(compareVersions(MustParseVersion("0.20.0"), float64(0.20)), qt.Equals, 0)
|
||||
c.Assert(compareVersions(MustParseVersion("0.19.1"), 0.20), qt.Equals, 1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.19.3"), "0.20.2"), qt.Equals, 1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.1"), 3), qt.Equals, 1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.1"), int32(3)), qt.Equals, 1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.1"), int64(3)), qt.Equals, 1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.20"), "0.20"), qt.Equals, 0)
|
||||
c.Assert(compareVersions(MustParseVersion("0.20.1"), "0.20.1"), qt.Equals, 0)
|
||||
c.Assert(compareVersions(MustParseVersion("0.20.1"), "0.20"), qt.Equals, -1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.20.0"), "0.20.1"), qt.Equals, 1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.20.1"), "0.20.2"), qt.Equals, 1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.21.1"), "0.22.1"), qt.Equals, 1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.22.0"), "0.22-DEV"), qt.Equals, -1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.22.0"), "0.22.1-DEV"), qt.Equals, 1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.22.0-DEV"), "0.22"), qt.Equals, 1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.22.1-DEV"), "0.22"), qt.Equals, -1)
|
||||
c.Assert(compareVersions(MustParseVersion("0.22.1-DEV"), "0.22.1-DEV"), qt.Equals, 0)
|
||||
}
|
||||
|
||||
func TestParseHugoVersion(t *testing.T) {
|
||||
|
||||
@@ -290,7 +290,7 @@ func InitGlobalLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, lo
|
||||
}
|
||||
|
||||
func getLogWriters(outHandle, logHandle io.Writer) (io.Writer, io.Writer) {
|
||||
isTerm := terminal.IsTerminal(os.Stdout)
|
||||
isTerm := terminal.PrintANSIColors(os.Stdout)
|
||||
if logHandle != ioutil.Discard && isTerm {
|
||||
// Remove any Ansi coloring from log output
|
||||
logHandle = ansiCleaner{w: logHandle}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2018 The Hugo Authors. All rights reserved.
|
||||
// Copyright 2022 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.
|
||||
@@ -29,6 +29,15 @@ const (
|
||||
noticeColor = "\033[1;36m%s\033[0m"
|
||||
)
|
||||
|
||||
// PrintANSIColors returns false if NO_COLOR env variable is set,
|
||||
// else IsTerminal(f).
|
||||
func PrintANSIColors(f *os.File) bool {
|
||||
if os.Getenv("NO_COLOR") != "" {
|
||||
return false
|
||||
}
|
||||
return IsTerminal(f)
|
||||
}
|
||||
|
||||
// IsTerminal return true if the file descriptor is terminal and the TERM
|
||||
// environment variable isn't a dumb one.
|
||||
func IsTerminal(f *os.File) bool {
|
||||
|
||||
@@ -83,7 +83,7 @@ func createPositionStringFormatter(formatStr string) func(p Position) string {
|
||||
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
|
||||
if terminal.IsTerminal(os.Stdout) {
|
||||
if terminal.PrintANSIColors(os.Stdout) {
|
||||
return terminal.Notice(msg)
|
||||
}
|
||||
|
||||
|
||||
@@ -61,3 +61,18 @@ func Puts(s string) string {
|
||||
}
|
||||
return s + "\n"
|
||||
}
|
||||
|
||||
// VisitLinesAfter calls the given function for each line, including newlines, in the given string.
|
||||
func VisitLinesAfter(s string, fn func(line string)) {
|
||||
high := strings.IndexRune(s, '\n')
|
||||
for high != -1 {
|
||||
fn(s[:high+1])
|
||||
s = s[high+1:]
|
||||
|
||||
high = strings.IndexRune(s, '\n')
|
||||
}
|
||||
|
||||
if s != "" {
|
||||
fn(s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,3 +41,36 @@ func TestPuts(t *testing.T) {
|
||||
c.Assert(Puts("\nA\n"), qt.Equals, "\nA\n")
|
||||
c.Assert(Puts(""), qt.Equals, "")
|
||||
}
|
||||
|
||||
func TestVisitLinesAfter(t *testing.T) {
|
||||
const lines = `line 1
|
||||
line 2
|
||||
|
||||
line 3`
|
||||
|
||||
var collected []string
|
||||
|
||||
VisitLinesAfter(lines, func(s string) {
|
||||
collected = append(collected, s)
|
||||
})
|
||||
|
||||
c := qt.New(t)
|
||||
|
||||
c.Assert(collected, qt.DeepEquals, []string{"line 1\n", "line 2\n", "\n", "line 3"})
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkVisitLinesAfter(b *testing.B) {
|
||||
const lines = `line 1
|
||||
line 2
|
||||
|
||||
line 3`
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
VisitLinesAfter(lines, func(s string) {
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ func decodeConfig(cfg config.Provider) (deployConfig, error) {
|
||||
return dcfg, err
|
||||
}
|
||||
for _, tgt := range dcfg.Targets {
|
||||
if tgt == nil {
|
||||
if *tgt == (target{}) {
|
||||
return dcfg, errors.New("empty deployment target")
|
||||
}
|
||||
if err := tgt.parseIncludeExclude(); err != nil {
|
||||
@@ -136,7 +136,7 @@ func decodeConfig(cfg config.Provider) (deployConfig, error) {
|
||||
}
|
||||
var err error
|
||||
for _, m := range dcfg.Matchers {
|
||||
if m == nil {
|
||||
if *m == (matcher{}) {
|
||||
return dcfg, errors.New("empty deployment matcher")
|
||||
}
|
||||
m.re, err = regexp.Compile(m.Pattern)
|
||||
|
||||
+5
-2
@@ -5,12 +5,15 @@ on: # rebuild any PRs and main branch changes
|
||||
- "dependabot/**"
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
spellcheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: streetsidesoftware/cspell-action@3bc52c39528d0214a947ff7f8b5c23ad3273435b
|
||||
- uses: actions/checkout@v3
|
||||
- uses: streetsidesoftware/cspell-action@v2
|
||||
with:
|
||||
inline: warning
|
||||
strict: false
|
||||
|
||||
@@ -19,7 +19,7 @@ toc: true
|
||||
|
||||
Hugo is a general-purpose website framework. Technically speaking, Hugo is a [static site generator][]. Unlike systems that dynamically build a page with each visitor request, Hugo builds pages when you create or update your content. Since websites are viewed far more often than they are edited, Hugo is designed to provide an optimal viewing experience for your website's end users and an ideal writing experience for website authors.
|
||||
|
||||
Websites built with Hugo are extremely fast and secure. Hugo sites can be hosted anywhere, including [Netlify][], [Heroku][], [GoDaddy][], [DreamHost][], [GitHub Pages][], [GitLab Pages][], [Surge][], [Aerobatic][], [Firebase][], [Google Cloud Storage][], [Amazon S3][], [Rackspace][], [Azure][], and [CloudFront][] and work well with CDNs. Hugo sites run without the need for a database or dependencies on expensive runtimes like Ruby, Python, or PHP.
|
||||
Websites built with Hugo are extremely fast and secure. Hugo sites can be hosted anywhere, including [Netlify][], [Heroku][], [GoDaddy][], [DreamHost][], [GitHub Pages][], [GitLab Pages][], [Surge][], [Firebase][], [Google Cloud Storage][], [Amazon S3][], [Rackspace][], [Azure][], and [CloudFront][] and work well with CDNs. Hugo sites run without the need for a database or dependencies on expensive runtimes like Ruby, Python, or PHP.
|
||||
|
||||
We think of Hugo as the ideal website creation tool with nearly instant build times, able to rebuild whenever a change is made.
|
||||
|
||||
@@ -40,7 +40,6 @@ Hugo is for people who want to hand code their own website without worrying abou
|
||||
Hugo is for people building a blog, a company site, a portfolio site, documentation, a single landing page, or a website with thousands of pages.
|
||||
|
||||
[@spf13]: https://twitter.com/spf13
|
||||
[Aerobatic]: https://www.aerobatic.com/
|
||||
[Amazon S3]: https://aws.amazon.com/s3/
|
||||
[Azure]: https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blob-static-website
|
||||
[CloudFront]: https://aws.amazon.com/cloudfront/ "Amazon CloudFront"
|
||||
|
||||
@@ -47,6 +47,7 @@ hugo [flags]
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
--minify minify any supported output format (HTML, XML etc.)
|
||||
--noBuildLock don't create .hugo_build.lock file
|
||||
--noChmod don't sync permission mode of files
|
||||
--noTimes don't sync modification time of files
|
||||
--panicOnWarning panic on first WARNING log
|
||||
|
||||
@@ -42,6 +42,7 @@ See https://gohugo.io/hugo-modules/ for more information.
|
||||
--ignoreCache ignores the cache directory
|
||||
-l, --layoutDir string filesystem path to layout directory
|
||||
--minify minify any supported output format (HTML, XML etc.)
|
||||
--noBuildLock don't create .hugo_build.lock file
|
||||
--noChmod don't sync permission mode of files
|
||||
--noTimes don't sync modification time of files
|
||||
--panicOnWarning panic on first WARNING log
|
||||
|
||||
@@ -43,6 +43,7 @@ hugo new [path] [flags]
|
||||
-k, --kind string content type to create
|
||||
-l, --layoutDir string filesystem path to layout directory
|
||||
--minify minify any supported output format (HTML, XML etc.)
|
||||
--noBuildLock don't create .hugo_build.lock file
|
||||
--noChmod don't sync permission mode of files
|
||||
--noTimes don't sync modification time of files
|
||||
--panicOnWarning panic on first WARNING log
|
||||
|
||||
@@ -54,6 +54,7 @@ hugo server [flags]
|
||||
--memstats string log memory usage to this file
|
||||
--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
|
||||
|
||||
@@ -126,4 +126,3 @@ refLinksNotFoundURL
|
||||
[lists]: /templates/lists/
|
||||
[output formats]: /templates/output-formats/
|
||||
[shortcode]: /content-management/shortcodes/
|
||||
[bfext]: /content-management/formats/#blackfriday-extensions
|
||||
|
||||
@@ -29,7 +29,6 @@ The current list of content formats in Hugo:
|
||||
| Name | Markup identifiers | Comment |
|
||||
| ------------- | ------------- |-------------|
|
||||
| Goldmark | md, markdown, goldmark |Note that you can set the default handler of `md` and `markdown` to something else, see [Configure Markup](/getting-started/configuration-markup/).{{< new-in "0.60.0" >}} |
|
||||
| Blackfriday | blackfriday |Blackfriday will eventually be deprecated.|
|
||||
|Emacs Org-Mode|org|See [go-org](https://github.com/niklasfasching/go-org).|
|
||||
|AsciiDoc|asciidocext, adoc, ad|Needs [Asciidoctor][ascii] installed.|
|
||||
|RST|rst|Needs [RST](https://docutils.sourceforge.io/rst.html) installed.|
|
||||
@@ -128,8 +127,6 @@ Markdown syntax is simple enough to learn in a single sitting. The following are
|
||||
|
||||
[`emojify` function]: /functions/emojify/
|
||||
[ascii]: https://asciidoctor.org/
|
||||
[bfconfig]: /getting-started/configuration/#configuring-blackfriday-rendering
|
||||
[blackfriday]: https://github.com/russross/blackfriday
|
||||
[config]: /getting-started/configuration/
|
||||
[developer tools]: /tools/
|
||||
[emojis]: https://www.webpagefx.com/tools/emoji-cheat-sheet/
|
||||
|
||||
@@ -217,7 +217,7 @@ You can assign content-specific `weight` in the front matter of your content. Th
|
||||
|
||||
## Override Global Markdown Configuration
|
||||
|
||||
It's possible to set some options for Markdown rendering in a content's front matter as an override to the [BlackFriday rendering options set in your project configuration][config].
|
||||
It's possible to set some options for Markdown rendering in a content's front matter as an override to the [Rendering options set in your project configuration][config].
|
||||
|
||||
## Front Matter Format Specs
|
||||
|
||||
|
||||
@@ -140,31 +140,6 @@ Press Ctrl+C to stop
|
||||
|
||||
Live reload and `--navigateToChanged` between the servers work as expected.
|
||||
|
||||
### Taxonomies and Blackfriday
|
||||
|
||||
Taxonomies and [Blackfriday configuration][config] can also be set per language:
|
||||
|
||||
{{< code-toggle file="config" >}}
|
||||
[Taxonomies]
|
||||
tag = "tags"
|
||||
|
||||
[blackfriday]
|
||||
angledQuotes = true
|
||||
hrefTargetBlank = true
|
||||
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
title = "English"
|
||||
[languages.en.blackfriday]
|
||||
angledQuotes = false
|
||||
|
||||
[languages.fr]
|
||||
weight = 2
|
||||
title = "Français"
|
||||
[languages.fr.Taxonomies]
|
||||
plaque = "plaques"
|
||||
{{</ code-toggle >}}
|
||||
|
||||
## Translate Your Content
|
||||
|
||||
@@ -544,10 +519,10 @@ Hugo will generate your website with these missing translation placeholders. It
|
||||
|
||||
For merging of content from other languages (i.e. missing content translations), see [lang.Merge].
|
||||
|
||||
To track down missing translation strings, run Hugo with the `--i18n-warnings` flag:
|
||||
To track down missing translation strings, run Hugo with the `--printI18nWarnings` flag:
|
||||
|
||||
```bash
|
||||
hugo --i18n-warnings | grep i18n
|
||||
hugo --printI18nWarnings | grep i18n
|
||||
i18n|MISSING_TRANSLATION|en|wordCount
|
||||
```
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ Options:
|
||||
* `linenostart=199`: starts the line number count from 199.
|
||||
* `anchorlinenos`: Configure anchors on line numbers. Valid values are `true` or `false`;
|
||||
* `lineanchors`: Configure a prefix for the anchors on line numbers. Will be suffixed with `-`, so linking to the line number 1 with the option `lineanchors=prefix` adds the anchor `prefix-1` to the page.
|
||||
* `hl_inline` Highlight inside a `<code>` (inline HTML element) tag. Valid values are `true` or `false`. The `code` tag will get a class with name `code-inline`. {{< new-in "0.101.0" >}}
|
||||
|
||||
### Example: Highlight Shortcode
|
||||
|
||||
|
||||
@@ -91,6 +91,9 @@ The following is a list of values that can be used in a `permalink` definition i
|
||||
`:slug`
|
||||
: the content's slug (or title if no slug is provided in the front matter)
|
||||
|
||||
`:slugorfilename`
|
||||
: the content's slug (or filename if no slug is provided in the front matter)
|
||||
|
||||
`:filename`
|
||||
: the content's filename (without extension)
|
||||
|
||||
|
||||
@@ -19,4 +19,3 @@ layout: documentation-home
|
||||
Hugo is the **world's fastest static website engine.** It's written in Go (aka Golang) and developed by [bep](https://github.com/bep), [spf13](https://github.com/spf13) and [friends](https://github.com/gohugoio/hugo/graphs/contributors).
|
||||
|
||||
Below you will find some of the most common and helpful pages from our documentation.
|
||||
foo
|
||||
|
||||
@@ -13,7 +13,7 @@ workson: []
|
||||
relatedfuncs: [humanize]
|
||||
---
|
||||
|
||||
If [Goldmark](https://gohugo.io/getting-started/configuration-markup#goldmark) is set as `defaultMarkdownHandler`, the sanitizing logic adheres to the setting [`markup.goldmark.parser.autoHeadingIDType`](https://gohugo.io/getting-started/configuration-markup#goldmark). If [Blackfriday](https://gohugo.io/getting-started/configuration-markup#blackfriday) is set as `defaultMarkdownHandler`, this template function uses the [`SanitizedAnchorName` logic from Blackfriday](https://github.com/russross/blackfriday#sanitized-anchor-names) (the same applies when `markup.goldmark.parser.autoHeadingIDType` is set to `blackfriday`).
|
||||
If [Goldmark](https://gohugo.io/getting-started/configuration-markup#goldmark) is set as `defaultMarkdownHandler`, the sanitizing logic adheres to the setting [`markup.goldmark.parser.autoHeadingIDType`](https://gohugo.io/getting-started/configuration-markup#goldmark).
|
||||
|
||||
Since the `defaultMarkdownHandler` and this template function use the same sanitizing logic, you can use the latter to determine the ID of a header for linking with anchor tags.
|
||||
|
||||
|
||||
@@ -54,6 +54,10 @@ hl_Lines
|
||||
: String. Default is `""`.\
|
||||
A space-separated list of lines to emphasize within the highlighted code. To emphasize lines 2, 3, 4, and 7, set this value to `2-4 7`. This option is independent of the `lineNoStart` option.
|
||||
|
||||
hl_inline
|
||||
: Boolean. Default is `false`.\
|
||||
Render the highlighted code without a wrapping container.
|
||||
|
||||
style
|
||||
: String. Default is `monokai`.\
|
||||
The CSS styles to apply to the highlighted code. See the [style gallery] for examples. Case-sensitive.
|
||||
|
||||
@@ -27,8 +27,8 @@ hugo.Generator
|
||||
hugo.Version
|
||||
: the current version of the Hugo binary you are using e.g. `0.63.2`
|
||||
|
||||
|
||||
`hugo` returns an instance that contains the following functions:
|
||||
hugo.GoVersion
|
||||
: returns the version of Go that the Hugo binary was built with. {{< new-in "0.101.0" >}}
|
||||
|
||||
hugo.Environment
|
||||
: the current running environment as defined through the `--environment` cli tag
|
||||
@@ -52,7 +52,6 @@ We highly recommend using `hugo.Generator` in your website's `<head>`. `hugo.Gen
|
||||
hugo.Deps
|
||||
: See [hugo.Deps](#hugodeps)
|
||||
|
||||
|
||||
## hugo.Deps
|
||||
|
||||
{{< new-in "0.92.0" >}}
|
||||
@@ -112,4 +111,4 @@ An example table listing the dependencies:
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
```
|
||||
```
|
||||
|
||||
@@ -12,7 +12,7 @@ keywords: [path, base]
|
||||
signature: ["path.Base PATH"]
|
||||
workson: []
|
||||
hugoversion: "0.40"
|
||||
relatedfuncs: [path.Dir, path.Ext, path.Split]
|
||||
relatedfuncs: [path.BaseName, path.Clean, path.Dir, path.Ext, path.Join, path.Split]
|
||||
deprecated: false
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
title: path.BaseName
|
||||
description: BaseName returns the last element of a path, removing the extension if present.
|
||||
date: 2022-06-04
|
||||
categories: [functions]
|
||||
menu:
|
||||
docs:
|
||||
parent: "functions"
|
||||
keywords: [path, base]
|
||||
signature: ["path.BaseName PATH"]
|
||||
relatedfuncs: [path.Base, path.Clean, path.Dir, path.Ext, path.Join, path.Split]
|
||||
deprecated: false
|
||||
---
|
||||
|
||||
If `PATH` is empty, `.` is returned.
|
||||
|
||||
**Note:** On Windows, `PATH` is converted to slash (`/`) separators.
|
||||
|
||||
```go-html-template
|
||||
{{ path.BaseName "a/news.html" }} → "news"
|
||||
{{ path.BaseName "news.html" }} → "news"
|
||||
{{ path.BaseName "a/b/c" }} → "c"
|
||||
{{ path.BaseName "/x/y/z/" }} → "z"
|
||||
```
|
||||
@@ -8,8 +8,9 @@ categories: [functions]
|
||||
menu:
|
||||
docs:
|
||||
parent: "functions"
|
||||
keywords: [path]
|
||||
keywords: [path, clean]
|
||||
signature: ["path.Clean PATH"]
|
||||
relatedfuncs: [path.Base, path.BaseName, path.Dir, path.Ext, path.Join, path.Split]
|
||||
---
|
||||
|
||||
`path.Clean` replaces path separators with slashes (`/`) and removes extraneous separators, including trailing separators.
|
||||
|
||||
@@ -12,7 +12,7 @@ keywords: [path, dir]
|
||||
signature: ["path.Dir PATH"]
|
||||
workson: []
|
||||
hugoversion: "0.40"
|
||||
relatedfuncs: [path.Base, path.Ext, path.Split]
|
||||
relatedfuncs: [path.Base, path.BaseName, path.Clean, path.Ext, path.Join, path.Split]
|
||||
deprecated: false
|
||||
---
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ keywords: [path, ext, extension]
|
||||
signature: ["path.Ext PATH"]
|
||||
workson: []
|
||||
hugoversion: "0.40"
|
||||
relatedfuncs: [path.Base, path.Dir, path.Split]
|
||||
relatedfuncs: [path.Base, path.BaseName, path.Clean, path.Dir, path.Join, path.Split]
|
||||
deprecated: false
|
||||
---
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ keywords: [path, join]
|
||||
signature: ["path.Join ELEMENT..."]
|
||||
workson: []
|
||||
hugoversion: "0.39"
|
||||
relatedfuncs: [path.Split]
|
||||
relatedfuncs: [path.Base, path.BaseName, path.Clean, path.Dir, path.Ext, path.Split]
|
||||
deprecated: false
|
||||
---
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ keywords: [path, split]
|
||||
signature: ["path.Split PATH"]
|
||||
workson: []
|
||||
hugoversion: "0.39"
|
||||
relatedfuncs: [path.Split]
|
||||
relatedfuncs: [path.Base, path.BaseName, path.Clean, path.Dir, path.Ext, path.Join]
|
||||
deprecated: false
|
||||
---
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ Below are all markup related configuration in Hugo with their default settings:
|
||||
|
||||
### Goldmark
|
||||
|
||||
[Goldmark](https://github.com/yuin/goldmark/) is from Hugo 0.60 the default library used for Markdown. It's fast, it's [CommonMark](https://spec.commonmark.org/0.29/) compliant and it's very flexible. Note that the feature set of Goldmark vs Blackfriday isn't the same; you gain a lot but also lose some, but we will work to bridge any gap in the upcoming Hugo versions.
|
||||
[Goldmark](https://github.com/yuin/goldmark/) is from Hugo 0.60 the default library used for Markdown. It's fast, it's [CommonMark](https://spec.commonmark.org/0.29/) compliant and it's very flexible.
|
||||
|
||||
This is the default configuration:
|
||||
|
||||
@@ -77,16 +77,8 @@ Note that attributes in [code fences](/content-management/syntax-highlighting/#h
|
||||
````
|
||||
|
||||
autoHeadingIDType ("github") {{< new-in "0.62.2" >}}
|
||||
: The strategy used for creating auto IDs (anchor names). Available types are `github`, `github-ascii` and `blackfriday`. `github` produces GitHub-compatible IDs, `github-ascii` will drop any non-Ascii characters after accent normalization, and `blackfriday` will make the IDs work as with [Blackfriday](#blackfriday), the default Markdown engine before Hugo 0.60. Note that if Goldmark is your default Markdown engine, this is also the strategy used in the [anchorize](/functions/anchorize/) template func.
|
||||
: The strategy used for creating auto IDs (anchor names). Available types are `github`, `github-ascii` and `blackfriday`. `github` produces GitHub-compatible IDs, `github-ascii` will drop any non-Ascii characters after accent normalization, and `blackfriday` will make the IDs compatible with [Blackfriday](#blackfriday), the default Markdown engine before Hugo 0.60. Note that if Goldmark is your default Markdown engine, this is also the strategy used in the [anchorize](/functions/anchorize/) template func.
|
||||
|
||||
### Blackfriday
|
||||
|
||||
|
||||
[Blackfriday](https://github.com/russross/blackfriday) was Hugo's default Markdown rendering engine, now replaced with Goldmark. But you can still use it: Just set `defaultMarkdownHandler` to `blackfriday` in your top level `markup` config.
|
||||
|
||||
This is the default config:
|
||||
|
||||
{{< code-toggle config="markup.blackFriday" />}}
|
||||
|
||||
### Highlight
|
||||
|
||||
|
||||
@@ -121,9 +121,6 @@ The directory where Hugo finds asset files used in [Hugo Pipes](/hugo-pipes/). {
|
||||
### baseURL
|
||||
Hostname (and path) to the root, e.g. https://bep.is/
|
||||
|
||||
### blackfriday
|
||||
See [Configure Blackfriday](/getting-started/configuration-markup#blackfriday)
|
||||
|
||||
### build
|
||||
See [Configure Build](#configure-build)
|
||||
|
||||
|
||||
@@ -43,56 +43,63 @@ Usage:
|
||||
hugo [command]
|
||||
|
||||
Available Commands:
|
||||
check Contains some verification checks
|
||||
completion Generate the autocompletion script for the specified shell
|
||||
config Print the site configuration
|
||||
convert Convert your content to different formats
|
||||
deploy Deploy your site to a Cloud provider.
|
||||
env Print Hugo version and environment info
|
||||
gen A collection of several useful generators.
|
||||
help Help about any command
|
||||
import Import your site from others.
|
||||
list Listing out various types of content
|
||||
mod Various Hugo Modules helpers.
|
||||
new Create new content for your site
|
||||
server A high performance webserver
|
||||
version Print the version number of Hugo
|
||||
|
||||
Flags:
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
-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. Defaults: $TMPDIR/hugo_cache/
|
||||
--cleanDestinationDir remove files from destination not found in static directories
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
--debug debug output
|
||||
-d, --destination string filesystem path to write files to
|
||||
--disableKinds strings disable different kind of pages (home, RSS etc.)
|
||||
--enableGitInfo add Git revision, date and author info to the pages
|
||||
-e, --environment string build environment
|
||||
--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 hugo
|
||||
--i18n-warnings print missing translations
|
||||
--ignoreCache ignores the cache directory
|
||||
-l, --layoutDir string filesystem path to layout directory
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
--minify minify any supported output format (HTML, XML etc.)
|
||||
--noChmod don't sync permission mode of files
|
||||
--noTimes don't sync modification time of files
|
||||
--path-warnings print warnings on duplicate target paths etc.
|
||||
--quiet build in quiet mode
|
||||
--renderToMemory render to memory (only useful for benchmark testing)
|
||||
-s, --source string filesystem path to read files relative from
|
||||
--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/)
|
||||
--themesDir string filesystem path to themes directory
|
||||
--trace file write trace to file (not useful in general)
|
||||
-v, --verbose verbose output
|
||||
--verboseLog verbose logging
|
||||
-w, --watch watch filesystem for changes and recreate as needed
|
||||
-b, --baseURL string hostname (and path) to the root, e.g. https://spf13.com/
|
||||
-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. Defaults: $TMPDIR/hugo_cache/
|
||||
--cleanDestinationDir remove files from destination not found in static directories
|
||||
--config string config file (default is path/config.yaml|json|toml)
|
||||
--configDir string config dir (default "config")
|
||||
-c, --contentDir string filesystem path to content directory
|
||||
--debug debug output
|
||||
-d, --destination string filesystem path to write files to
|
||||
--disableKinds strings disable different kind of pages (home, RSS etc.)
|
||||
--enableGitInfo add Git revision, date, author, and CODEOWNERS info to the pages
|
||||
-e, --environment string build environment
|
||||
--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 hugo
|
||||
--ignoreCache ignores the cache directory
|
||||
--ignoreVendorPaths string ignores any _vendor for module paths matching the given Glob pattern
|
||||
-l, --layoutDir string filesystem path to layout directory
|
||||
--log enable Logging
|
||||
--logFile string log File path (if set, logging enabled automatically)
|
||||
--minify minify any supported output format (HTML, XML etc.)
|
||||
--noChmod don't sync permission mode of files
|
||||
--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
|
||||
--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.
|
||||
--quiet build in quiet mode
|
||||
--renderToMemory render to memory (only useful for benchmark testing)
|
||||
-s, --source string filesystem path to read files relative from
|
||||
--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/)
|
||||
--themesDir string filesystem path to themes directory
|
||||
--trace file write trace to file (not useful in general)
|
||||
-v, --verbose verbose output
|
||||
--verboseLog verbose logging
|
||||
-w, --watch watch filesystem for changes and recreate as needed
|
||||
|
||||
Use "hugo [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
@@ -53,6 +53,17 @@ With `resources.GetRemote`, the first argument is a remote URL:
|
||||
|
||||
`resources.Get` and `resources.GetRemote` return `nil` if the resource is not found.
|
||||
|
||||
## Copy a Resource
|
||||
|
||||
{{< new-in "0.100.0" >}}
|
||||
|
||||
`resources.Copy` allows you to copy almost any Hugo `Resource` (the one exception is the `Page`), possibly most useful for renaming things:
|
||||
|
||||
```go-html-template
|
||||
{{ $resized := $image.Resize "400x400" | resources.Copy "images/mynewname.jpg" }}
|
||||
<img src="{{ $resized.RelPermalink }}">
|
||||
```
|
||||
|
||||
### Caching
|
||||
|
||||
By default, Hugo calculates a cache key based on the `URL` and the `options` (e.g. headers) given.
|
||||
|
||||
@@ -45,7 +45,7 @@ includePaths [string slice]
|
||||
: Additional SCSS/SASS include paths. Paths must be relative to the project directory.
|
||||
|
||||
```go-html-template
|
||||
{{ $options := (dict "targetPath" "style.css" "outputStyle" "compressed" "enableSourceMap" true "includePaths" (slice "node_modules/myscss")) }}
|
||||
{{ $options := (dict "targetPath" "style.css" "outputStyle" "compressed" "enableSourceMap" (not hugo.IsProduction) "includePaths" (slice "node_modules/myscss")) }}
|
||||
{{ $style := resources.Get "sass/main.scss" | resources.ToCSS $options }}
|
||||
```
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ siteURL: https://www.tomango.co.uk
|
||||
|
||||
siteSource: https://github.com/trys/tomango-2018
|
||||
|
||||
byline: "[Trys Mudford](http://www.trysmudford.com), Lead Developer, Tomango"
|
||||
byline: "[Trys Mudford](https://www.trysmudford.com), Lead Developer, Tomango"
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ aliases: []
|
||||
toc: false
|
||||
---
|
||||
|
||||
When using Hugo with [GitHub Pages](https://pages.github.com/), you can provide your own template for a [custom 404 error page](https://help.github.com/articles/custom-404-pages/) by creating a 404.html template file in your `/layouts` folder. When Hugo generates your site, the `404.html` file will be placed in the root.
|
||||
When using Hugo with [GitHub Pages](https://pages.github.com/), you can provide your own template for a [custom 404 error page](https://docs.github.com/en/pages/getting-started-with-github-pages/creating-a-custom-404-page-for-your-github-pages-site) by creating a 404.html template file in your `/layouts` folder. When Hugo generates your site, the `404.html` file will be placed in the root.
|
||||
|
||||
404 pages will have all the regular [page variables][pagevars] available to use in the templates.
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ You can use the following code to render the `Short Description` in your layout:
|
||||
<div>Short Description of {{.Site.Data.User0123.Name}}: <p>{{ index .Site.Data.User0123 "Short Description" | markdownify }}</p></div>
|
||||
```
|
||||
|
||||
Note the use of the [`markdownify` template function][markdownify]. This will send the description through the Blackfriday Markdown rendering engine.
|
||||
Note the use of the [`markdownify` template function][markdownify]. This will send the description through the Markdown rendering engine.
|
||||
|
||||
|
||||
## Get Remote Data
|
||||
|
||||
@@ -43,7 +43,7 @@ To use the `readFile` function in your templates, make sure the path is relative
|
||||
|
||||
### `readFile` Example: Add a Project File to Content
|
||||
|
||||
As `readFile` is a function, it is only available to you in your templates and not your content. However, we can create a simple [shortcode template][sct] that calls `readFile`, passes the first argument through the function, and then allows an optional second argument to send the file through the Blackfriday markdown processor. The pattern for adding this shortcode to your content will be as follows:
|
||||
As `readFile` is a function, it is only available to you in your templates and not your content. However, we can create a simple [shortcode template][sct] that calls `readFile`, passes the first argument through the function, and then allows an optional second argument to send the file through the markdown processor. The pattern for adding this shortcode to your content will be as follows:
|
||||
|
||||
```
|
||||
{{</* readfile file="/path/to/local/file.txt" markdown="true" */>}}
|
||||
|
||||
@@ -107,7 +107,7 @@ The custom variables need to be prefixed with `$`.
|
||||
{{ $address := "123 Main St." }}
|
||||
{{ $address }}
|
||||
```
|
||||
Vriables can be re-defined using the `=` operator. The example below
|
||||
Variables can be re-defined using the `=` operator. The example below
|
||||
prints "Var is Hugo Home" on the home page, and "Var is Hugo Page" on
|
||||
all other pages:
|
||||
|
||||
|
||||
@@ -45,9 +45,9 @@ Also see this Twitter thread:
|
||||
|
||||
Yes you can! Read [this](/hosting-and-deployment/hosting-on-netlify/#configure-hugo-version-in-netlify).
|
||||
|
||||
## I get "TOCSS ... this feature is not available in your current Hugo version"
|
||||
## I get "... this feature is not available in your current Hugo version"
|
||||
|
||||
If you process `SCSS` or `SASS` to `CSS` in your Hugo project, you need the Hugo `extended` version, or else you may see this error message:
|
||||
If you process `SCSS` or `SASS` to `CSS` in your Hugo project with `libsass` as the transpiler or if you convert images to the `webp` format, you need the Hugo `extended` version, or else you may see an error message similar to the below:
|
||||
|
||||
```bash
|
||||
error: failed to transform resource: TOCSS: failed to transform "scss/main.scss" (text/x-scss): this feature is not available in your current Hugo version
|
||||
|
||||
@@ -45,4 +45,8 @@ toc: false
|
||||
[markdownshortcode]: /content-management/shortcodes/#shortcodes-with-markdown
|
||||
[shortcodes]: /templates/shortcode-templates/
|
||||
|
||||
.InnerDeindent {{< new-in "0.100.0" >}}
|
||||
: Gets the `.Inner` with any indentation removed. This is what's used in the built-in `{{</* highlight */>}}` shortcode.
|
||||
|
||||
|
||||
|
||||
|
||||
+26
-26
@@ -1,49 +1,49 @@
|
||||
---
|
||||
title: "世界上最快的网站构建框架 The world’s fastest framework for building websites"
|
||||
title: "世界上最快的网站构建框架"
|
||||
date: 2017-03-02T12:00:00-05:00
|
||||
features:
|
||||
- heading: Blistering Speed
|
||||
- heading: 飞快的速度
|
||||
image_path: /images/icon-fast.svg
|
||||
tagline: What's modern about waiting for your site to build?
|
||||
copy: Hugo is the fastest tool of its kind. At <1 ms per page, the average site builds in less than a second.
|
||||
tagline: 摩登发布
|
||||
copy: Hugo 是同类中最快的工具。生成一页小于 1ms,生成一个站点平均不超过 1s。
|
||||
|
||||
- heading: Robust Content Management
|
||||
- heading: 强大的内容管理
|
||||
image_path: /images/icon-content-management.svg
|
||||
tagline: Flexibility rules. Hugo is a content strategist's dream.
|
||||
copy: Hugo supports unlimited content types, taxonomies, menus, dynamic API-driven content, and more, all without plugins.
|
||||
tagline: 灵活的规则。Hugo 是一个设计师的梦想。
|
||||
copy: Hugo 支持无限的内容类型、分类、菜单、动态 API 驱动的内容,以及更多,都不需要插件。
|
||||
|
||||
- heading: Shortcodes
|
||||
- heading: 短代码
|
||||
image_path: /images/icon-shortcodes.svg
|
||||
tagline: Hugo's shortcodes are Markdown's hidden superpower.
|
||||
copy: We love the beautiful simplicity of markdown’s syntax, but there are times when we want more flexibility. Hugo shortcodes allow for both beauty and flexibility.
|
||||
tagline: Hugo 短代码是 Markdown 隐藏的强大功能。
|
||||
copy: 我们喜欢美丽简洁的 markdown 语法,但是有时候我们也想要更灵活一些。Hugo 短代码允许既美丽又灵活。
|
||||
|
||||
- heading: Built-in Templates
|
||||
- heading: 内置模板
|
||||
image_path: /images/icon-built-in-templates.svg
|
||||
tagline: Hugo has common patterns to get your work done quickly.
|
||||
copy: Hugo ships with pre-made templates to make quick work of SEO, commenting, analytics and other functions. One line of code, and you're done.
|
||||
tagline: Hugo 有通用的模式让你快速的完成工作。
|
||||
copy: Hugo 平台预置的模板会快速实现搜索引擎优化、评论、分析和其他的功能。一行代码,你就完成了。
|
||||
|
||||
- heading: Multilingual and i18n
|
||||
- heading: 多语言和国际化
|
||||
image_path: /images/icon-multilingual2.svg
|
||||
tagline: Polyglot baked in.
|
||||
copy: Hugo provides full i18n support for multi-language sites with the same straightforward development experience Hugo users love in single-language sites.
|
||||
tagline: 多语言制作
|
||||
copy: Hugo 为多语言站点提供了完整的国际化支持,并且具有与 Hugo 用户在单语言站点中喜爱的相同的简单开发体验。
|
||||
|
||||
- heading: Custom Outputs
|
||||
- heading: 自定义输出
|
||||
image_path: /images/icon-custom-outputs.svg
|
||||
tagline: HTML not enough?
|
||||
copy: Hugo allows you to output your content in multiple formats, including JSON or AMP, and makes it easy to create your own.
|
||||
tagline: HTML 不够用吗?
|
||||
copy: Hugo 允许您以多种格式输出内容,包括 JSON 或 AMP,并且可以很容易地创建自己的内容。
|
||||
sections:
|
||||
- heading: "100s of Themes"
|
||||
cta: Check out the Hugo's themes.
|
||||
- heading: "100 多个主题"
|
||||
cta: 看看 Hugo 的主题
|
||||
link: https://themes.gohugo.io/
|
||||
color_classes: bg-accent-color white
|
||||
image: /images/homepage-screenshot-hugo-themes.jpg
|
||||
copy: "Hugo provides a robust theming system that is easy to implement but capable of producing even the most complicated websites."
|
||||
- heading: "Capable Templating"
|
||||
cta: Get Started.
|
||||
copy: "Hugo 提供了一个强大的主题系统,易于实现,但能够生成即使是最复杂的网站。"
|
||||
- heading: "功能模板"
|
||||
cta: 开始吧。
|
||||
link: templates/
|
||||
color_classes: bg-primary-color-light black
|
||||
image: /images/home-page-templating-example.png
|
||||
copy: "Hugo's Go-based templating provides just the right amount of logic to build anything from the simple to complex. If you prefer Jade/Pug-like syntax, you can also use Amber, Ace, or any combination of the three."
|
||||
copy: "Hugo 基于 Go 的模板提供了恰当的方法来生成从简单到复杂的任何东西。如果你喜欢 Jade/Pug 类似的语法,你也可以使用 Amber、Ace 或三种任意组合。"
|
||||
---
|
||||
|
||||
Hugo is one of the most popular open-source static site generators. With its amazing speed and flexibility, Hugo makes building websites fun again.
|
||||
Hugo 是最受欢迎的开源静态站点生成器之一。凭借惊人的速度和灵活性,Hugo 使建设网站的乐趣再现。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Hugo 说明文档
|
||||
linktitle: Hugo
|
||||
description: Hugo is the world's fastest static website engine. It's written in Go (aka Golang) and developed by bep, spf13 and friends.
|
||||
description: Hugo 是世界上最快的静态网站引擎。它是用 Go (即 Golang) 编程语言所写成,由 bep、spf13 和朋友们共同开发。
|
||||
date: 2017-02-01
|
||||
publishdate: 2017-02-01
|
||||
menu:
|
||||
@@ -16,6 +16,6 @@ toc: false
|
||||
layout: documentation-home
|
||||
isCJKLanguage: true
|
||||
---
|
||||
Hugo 号称**世界上最快的静态网站引擎**。它是以 Go (即 Golang) 编程语言所写成,并由 [bep](https://github.com/bep)、[spf13](https://github.com/spf13) 和[朋友们](https://github.com/gohugoio/hugo/graphs/contributors) 共同开发。
|
||||
Hugo 是世界上最快的静态网站引擎。它是用 Go (即 Golang) 编程语言所写成,由 [bep](https://github.com/bep)、[spf13](https://github.com/spf13) 和[朋友们](https://github.com/gohugoio/hugo/graphs/contributors)共同开发。
|
||||
|
||||
下面是我们说明文档中最常用和实用的章节:
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: 开发者工具
|
||||
linktitle: 开发者工具概览
|
||||
description: 除了 Hugo 强大的命令外,对于 Hugo 开发者还有大量社区开发的工具链。
|
||||
date: 2018-05-29
|
||||
publishdate: 2018-05-29
|
||||
lastmod: 2018-05-29
|
||||
categories: [developer tools]
|
||||
keywords: []
|
||||
menu:
|
||||
docs:
|
||||
parent: "tools"
|
||||
weight: 01
|
||||
weight: 01
|
||||
sections_weight: 01
|
||||
draft: false
|
||||
aliases: [/tools/]
|
||||
---
|
||||
|
||||
Hugo 最强大的在于它是活跃的——并总在发展的——开发者社区。除了在[语法高亮][syntax] 中提到的 `highlight` 简码外,该章节中的这些工具和其它项目特色都是由商业服务和开源项目提供,很多就是由像你一样的 Hugo 开发者开发的。
|
||||
|
||||
[查看 Hugo 的流行程度与其它静态网站生成器的比较。][staticgen]
|
||||
|
||||
[staticgen]: https://staticgen.com
|
||||
[syntax]: /tools/syntax-highlighting/
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
title: 搜索你的 Hugo 网站
|
||||
linktitle: 搜索
|
||||
description: 为你最新添加的 Hugo 网站看一些开源和商业搜索选择。
|
||||
date: 2018-05-29
|
||||
publishdate: 2018-05-29
|
||||
lastmod: 2018-05-29
|
||||
categories: [developer tools]
|
||||
keywords: [search,tools]
|
||||
menu:
|
||||
docs:
|
||||
parent: "tools"
|
||||
weight: 60
|
||||
weight: 60
|
||||
sections_weight: 60
|
||||
draft: false
|
||||
aliases: []
|
||||
toc: true
|
||||
---
|
||||
|
||||
静态网站还有动态搜索功能?是的。作为可选方案,来自 Google 或者其它搜索引擎的嵌入式脚本,可以给你的访客提供一个自定义的直接基于你的内容文件索引的搜素。
|
||||
|
||||
* [GitHub Gist for Hugo Workflow](https://gist.github.com/sebz/efddfc8fdcb6b480f567). Gist 包含一个为你的网站创建索引的简单流程。它使用简单的 Grunt 脚本索引你所有的内容文件,并且 [lunr.js](http://lunrjs.com/) 会提供搜索结果。
|
||||
* [hugo-lunr](https://www.npmjs.com/package/hugo-lunr). 一个使用 [lunr.js](http://lunrjs.com/) 为你的 Hugo 静态网站添加搜索的简单方法。Hugo-lunr 将会给你的 Hugo 项目中任意 html 和 markdown 文件创建一个索引文件。
|
||||
* [hugo-lunr-zh](https://www.npmjs.com/package/hugo-lunr-zh). 有点像 Hugo-lunr,但是 Hugo-lunr-zh 能帮助你分割中文关键字。
|
||||
* [Github Gist for Fuse.js integration](https://gist.github.com/eddiewebb/735feb48f50f0ddd65ae5606a1cb41ae). 该 gist 显示如何借助 Hugo 已有的构建时间在客户端通过 [Fuse.js](http://fusejs.io/) 生成可搜索的 JSON 索引。尽管 gist 使用 Fuse.js 进行模糊匹配,任何有能力读取 JSON 索引的客户端工具都可以运行。不需要 Hugo 以外, npm、grunt 或者其它构建时工具。
|
||||
* [hugo-search-index](https://www.npmjs.com/package/hugo-search-index). 一个包含实现了搜索的 Gulp 任务和内置浏览器脚本的库。Gulp 从项目中的 markdown 文件生成索引。
|
||||
|
||||
## 商业搜索服务
|
||||
|
||||
* [Algolia](https://www.algolia.com/) 的搜索 API 使得在你的应用和网站中提供一个很好的搜索体验变得简单。Algolia 搜索提供托管的全文本、数字化、分面以及地理定位搜索。
|
||||
+329
-509
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
<dl>
|
||||
{{ range .Site.Data.docs.chroma.lexers }}
|
||||
<dt>{{ .Name }}</dt>
|
||||
<dd>{{ delimit .Aliases ", " }}</dd>
|
||||
{{ end }}
|
||||
</dl>
|
||||
{{ range .Site.Data.docs.chroma.lexers }}
|
||||
<dt>{{ .Name }}</dt>
|
||||
<dd>{{ with .Aliases }}{{ delimit . ", " }}{{ end }}</dd>
|
||||
{{ end }}
|
||||
</dl>
|
||||
|
||||
+4
-4
@@ -3,7 +3,7 @@ publish = "public"
|
||||
command = "hugo --gc --minify"
|
||||
|
||||
[context.production.environment]
|
||||
HUGO_VERSION = "0.98.0"
|
||||
HUGO_VERSION = "0.101.0"
|
||||
HUGO_ENV = "production"
|
||||
HUGO_ENABLEGITINFO = "true"
|
||||
|
||||
@@ -11,20 +11,20 @@ HUGO_ENABLEGITINFO = "true"
|
||||
command = "hugo --gc --minify --enableGitInfo"
|
||||
|
||||
[context.split1.environment]
|
||||
HUGO_VERSION = "0.98.0"
|
||||
HUGO_VERSION = "0.101.0"
|
||||
HUGO_ENV = "production"
|
||||
|
||||
[context.deploy-preview]
|
||||
command = "hugo --gc --minify --buildFuture -b $DEPLOY_PRIME_URL"
|
||||
|
||||
[context.deploy-preview.environment]
|
||||
HUGO_VERSION = "0.98.0"
|
||||
HUGO_VERSION = "0.101.0"
|
||||
|
||||
[context.branch-deploy]
|
||||
command = "hugo --gc --minify -b $DEPLOY_PRIME_URL"
|
||||
|
||||
[context.branch-deploy.environment]
|
||||
HUGO_VERSION = "0.98.0"
|
||||
HUGO_VERSION = "0.101.0"
|
||||
|
||||
[context.next.environment]
|
||||
HUGO_ENABLEGITINFO = "true"
|
||||
|
||||
@@ -3,7 +3,7 @@ module github.com/gohugoio/hugo
|
||||
require (
|
||||
github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69
|
||||
github.com/PuerkitoBio/purell v1.1.1
|
||||
github.com/alecthomas/chroma v0.10.0
|
||||
github.com/alecthomas/chroma/v2 v2.2.0
|
||||
github.com/armon/go-radix v1.0.0
|
||||
github.com/aws/aws-sdk-go v1.43.5
|
||||
github.com/bep/clock v0.3.0
|
||||
@@ -15,15 +15,15 @@ require (
|
||||
github.com/bep/gowebp v0.1.0
|
||||
github.com/bep/overlayfs v0.6.0
|
||||
github.com/bep/tmc v0.5.1
|
||||
github.com/clbanning/mxj/v2 v2.5.5
|
||||
github.com/clbanning/mxj/v2 v2.5.6
|
||||
github.com/cli/safeexec v1.0.0
|
||||
github.com/disintegration/gift v1.2.1
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/evanw/esbuild v0.14.39
|
||||
github.com/evanw/esbuild v0.14.43
|
||||
github.com/fortytw2/leaktest v1.3.0
|
||||
github.com/frankban/quicktest v1.14.3
|
||||
github.com/fsnotify/fsnotify v1.5.4
|
||||
github.com/getkin/kin-openapi v0.94.0
|
||||
github.com/getkin/kin-openapi v0.97.0
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/gobuffalo/flect v0.2.5
|
||||
github.com/gobwas/glob v0.2.3
|
||||
@@ -42,29 +42,28 @@ require (
|
||||
github.com/mitchellh/hashstructure v1.1.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/muesli/smartcrop v0.3.0
|
||||
github.com/niklasfasching/go-org v1.6.2
|
||||
github.com/niklasfasching/go-org v1.6.5
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/pelletier/go-toml/v2 v2.0.0-beta.7.0.20220408132554-2377ac4bc04c
|
||||
github.com/pelletier/go-toml/v2 v2.0.2
|
||||
github.com/rogpeppe/go-internal v1.8.1
|
||||
github.com/russross/blackfriday v1.6.0
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd
|
||||
github.com/sanity-io/litter v1.5.4
|
||||
github.com/sanity-io/litter v1.5.5
|
||||
github.com/spf13/afero v1.8.2
|
||||
github.com/spf13/cast v1.5.0
|
||||
github.com/spf13/cobra v1.4.0
|
||||
github.com/spf13/fsync v0.9.0
|
||||
github.com/spf13/jwalterweatherman v1.1.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/tdewolff/minify/v2 v2.11.2
|
||||
github.com/tdewolff/parse/v2 v2.5.29
|
||||
github.com/tdewolff/minify/v2 v2.11.10
|
||||
github.com/tdewolff/parse/v2 v2.6.0
|
||||
github.com/yuin/goldmark v1.4.12
|
||||
go.uber.org/atomic v1.9.0
|
||||
gocloud.dev v0.24.0
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/text v0.3.7
|
||||
golang.org/x/tools v0.1.10
|
||||
golang.org/x/tools v0.1.11
|
||||
google.golang.org/api v0.76.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
@@ -104,6 +103,7 @@ require (
|
||||
github.com/googleapis/gax-go/v2 v2.3.0 // indirect
|
||||
github.com/googleapis/go-type-adapters v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/invopop/yaml v0.1.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/kr/pretty v0.3.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
@@ -115,12 +115,13 @@ require (
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46 // indirect
|
||||
google.golang.org/grpc v1.46.0 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
go 1.18
|
||||
|
||||
@@ -131,8 +131,11 @@ github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tN
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
|
||||
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
|
||||
github.com/alecthomas/chroma/v2 v2.1.0 h1:ZG9L5/RsxO/xIONrBy8Cgo+5si3d9x3osweXc4VHl0o=
|
||||
github.com/alecthomas/chroma/v2 v2.1.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||
github.com/alecthomas/chroma/v2 v2.2.0 h1:Aten8jfQwUqEdadVFFjNyjx7HTexhKP0XuqBG67mRDY=
|
||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae h1:zzGwJfFlFGD94CyyYwCJeSuD32Gj9GTaSi5y9hoVzdY=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
@@ -173,8 +176,6 @@ github.com/bep/goat v0.5.0 h1:S8jLXHCVy/EHIoCY+btKkmcxcXFd34a0Q63/0D4TKeA=
|
||||
github.com/bep/goat v0.5.0/go.mod h1:Md9x7gRxiWKs85yHlVTvHQw9rg86Bm+Y4SuYE8CTH7c=
|
||||
github.com/bep/godartsass v0.14.0 h1:pPb6XkpyDEppS+wK0veh7OXDQc4xzOJI9Qcjb743UeQ=
|
||||
github.com/bep/godartsass v0.14.0/go.mod h1:6LvK9RftsXMxGfsA0LDV12AGc4Jylnu6NgHL+Q5/pE8=
|
||||
github.com/bep/golibsass v1.0.0 h1:gNguBMSDi5yZEZzVZP70YpuFQE3qogJIGUlrVILTmOw=
|
||||
github.com/bep/golibsass v1.0.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA=
|
||||
github.com/bep/golibsass v1.1.0 h1:pjtXr00IJZZaOdfryNa9wARTB3Q0BmxC3/V1KNcgyTw=
|
||||
github.com/bep/golibsass v1.1.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA=
|
||||
github.com/bep/gowebp v0.1.0 h1:4/iQpfnxHyXs3x/aTxMMdOpLEQQhFmF6G7EieWPTQyo=
|
||||
@@ -193,8 +194,8 @@ github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgk
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E=
|
||||
github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
github.com/clbanning/mxj/v2 v2.5.6 h1:Jm4VaCI/+Ug5Q57IzEoZbwx4iQFA6wkXv72juUSeK+g=
|
||||
github.com/clbanning/mxj/v2 v2.5.6/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=
|
||||
github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
@@ -238,8 +239,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/evanw/esbuild v0.14.39 h1:1TMZtCXOY4ctAbGY4QT9sjT203I/cQ16vXt2F9rLT58=
|
||||
github.com/evanw/esbuild v0.14.39/go.mod h1:GG+zjdi59yh3ehDn4ZWfPcATxjPDUH53iU4ZJbp7dkY=
|
||||
github.com/evanw/esbuild v0.14.43 h1:4WIYkAf2fLmru3KYtKUB6mdBl4dgpoVcq7hqXhXGVG0=
|
||||
github.com/evanw/esbuild v0.14.43/go.mod h1:GG+zjdi59yh3ehDn4ZWfPcATxjPDUH53iU4ZJbp7dkY=
|
||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
@@ -250,11 +251,10 @@ github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUork
|
||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/fsnotify/fsnotify v1.5.3/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/getkin/kin-openapi v0.94.0 h1:bAxg2vxgnHHHoeefVdmGbR+oxtJlcv5HsJJa3qmAHuo=
|
||||
github.com/getkin/kin-openapi v0.94.0/go.mod h1:LWZfzOd7PRy8GJ1dJ6mCU6tNdSfOwRac1BUPam4aw6Q=
|
||||
github.com/getkin/kin-openapi v0.97.0 h1:bsvXZeuGiCW43ZKy6xOY5qfT5fCRYmnJwierblSrHCU=
|
||||
github.com/getkin/kin-openapi v0.97.0/go.mod h1:w4lRPHiyOdwGbOkLIyk+P0qCwlu7TXPCHD/64nSXzgE=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
@@ -403,6 +403,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/invopop/yaml v0.1.0 h1:YW3WGUoJEXYfzWBjn00zIlrw7brGVD0fUKRYDPAPhrc=
|
||||
github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
|
||||
github.com/jdkato/prose v1.2.1 h1:Fp3UnJmLVISmlc57BgKUzdjr0lOtjqTZicL3PaYy6cU=
|
||||
github.com/jdkato/prose v1.2.1/go.mod h1:AiRHgVagnEx2JbQRQowVBKjG0bcs/vtkGCH1dYAL1rA=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
@@ -461,12 +463,12 @@ github.com/muesli/smartcrop v0.3.0/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78Rwc
|
||||
github.com/neurosnap/sentences v1.0.6/go.mod h1:pg1IapvYpWCJJm/Etxeh0+gtMf1rI1STY9S7eUCPbDc=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/niklasfasching/go-org v1.6.2 h1:kQBIZlfL4oRNApJCrBgaeNBfzxWzP6XlC7/b744Polk=
|
||||
github.com/niklasfasching/go-org v1.6.2/go.mod h1:wn76Xgu4/KRe43WZhsgZjxYMaloSrl3BSweGV74SwHs=
|
||||
github.com/niklasfasching/go-org v1.6.5 h1:5YAIqNTdl6lAOb7lD2AyQ1RuFGPVrAKvUexphk8PGbo=
|
||||
github.com/niklasfasching/go-org v1.6.5/go.mod h1:ybv0eGDnxylFUfFE+ySaQc734j/L3+/ChKZ/h63a2wM=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/pelletier/go-toml/v2 v2.0.0-beta.7.0.20220408132554-2377ac4bc04c h1:tgrA7vMkP4q9diPCg3u4Ka4/g3fvkifBu1y9DL+uy7M=
|
||||
github.com/pelletier/go-toml/v2 v2.0.0-beta.7.0.20220408132554-2377ac4bc04c/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||
github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw=
|
||||
github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
@@ -481,14 +483,12 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
|
||||
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
||||
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
|
||||
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/sanity-io/litter v1.5.4 h1:3Fvo2hKVtmA0XFIHkFQ4cHxA0EemTqBA03WttcB3YZA=
|
||||
github.com/sanity-io/litter v1.5.4/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
|
||||
github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
|
||||
github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
|
||||
github.com/shogo82148/go-shuffle v0.0.0-20180218125048-27e6095f230d/go.mod h1:2htx6lmL0NGLHlO8ZCf+lQBGBHIbEujyywxJArf+2Yc=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
@@ -513,12 +513,12 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tdewolff/minify/v2 v2.11.2 h1:PpaPWhNlMVjkAKaOj0bbPv6KCVnrm8jbVwG7OtSdAqw=
|
||||
github.com/tdewolff/minify/v2 v2.11.2/go.mod h1:NxozhBtgUVypPLzQdV96wkIu9J9vAiVmBcKhfC2zMfg=
|
||||
github.com/tdewolff/parse/v2 v2.5.29 h1:Uf0OtZL9YaUXTuHEOitdo9lD90P0XTwCjZi+KbGChuM=
|
||||
github.com/tdewolff/parse/v2 v2.5.29/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
|
||||
github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/tdewolff/minify/v2 v2.11.10 h1:2tk9nuKfc8YOTD8glZ7JF/VtE8W5HOgmepWdjcPtRro=
|
||||
github.com/tdewolff/minify/v2 v2.11.10/go.mod h1:dHOS3dk+nJ0M3q3uM3VlNzTb70cou+ov0ki7C4PAFgM=
|
||||
github.com/tdewolff/parse/v2 v2.6.0 h1:f2D7w32JtqjCv6SczWkfwK+m15et42qEtDnZXHoNY70=
|
||||
github.com/tdewolff/parse/v2 v2.6.0/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
|
||||
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
|
||||
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
@@ -647,8 +647,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d h1:4SFsTMi4UahlKoloni7L4eYzhFRifURQLw+yv0QDCx8=
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
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=
|
||||
@@ -746,8 +746,8 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc=
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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=
|
||||
@@ -818,8 +818,8 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY=
|
||||
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
|
||||
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=
|
||||
@@ -1017,8 +1017,10 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
@@ -34,7 +34,6 @@ import (
|
||||
|
||||
"github.com/gohugoio/hugo/markup"
|
||||
|
||||
bp "github.com/gohugoio/hugo/bufferpool"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
)
|
||||
|
||||
@@ -104,45 +103,6 @@ func NewContentSpec(cfg config.Provider, logger loggers.Logger, contentFs afero.
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
var stripHTMLReplacer = strings.NewReplacer("\n", " ", "</p>", "\n", "<br>", "\n", "<br />", "\n")
|
||||
|
||||
// StripHTML accepts a string, strips out all HTML tags and returns it.
|
||||
func StripHTML(s string) string {
|
||||
// Shortcut strings with no tags in them
|
||||
if !strings.ContainsAny(s, "<>") {
|
||||
return s
|
||||
}
|
||||
s = stripHTMLReplacer.Replace(s)
|
||||
|
||||
// Walk through the string removing all tags
|
||||
b := bp.GetBuffer()
|
||||
defer bp.PutBuffer(b)
|
||||
var inTag, isSpace, wasSpace bool
|
||||
for _, r := range s {
|
||||
if !inTag {
|
||||
isSpace = false
|
||||
}
|
||||
|
||||
switch {
|
||||
case r == '<':
|
||||
inTag = true
|
||||
case r == '>':
|
||||
inTag = false
|
||||
case unicode.IsSpace(r):
|
||||
isSpace = true
|
||||
fallthrough
|
||||
default:
|
||||
if !inTag && (!isSpace || (isSpace && !wasSpace)) {
|
||||
b.WriteRune(r)
|
||||
}
|
||||
}
|
||||
|
||||
wasSpace = isSpace
|
||||
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// stripEmptyNav strips out empty <nav> tags from content.
|
||||
func stripEmptyNav(in []byte) []byte {
|
||||
return bytes.Replace(in, []byte("<nav>\n</nav>\n\n"), []byte(``), -1)
|
||||
|
||||
@@ -52,44 +52,6 @@ func TestTrimShortHTML(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripHTML(t *testing.T) {
|
||||
type test struct {
|
||||
input, expected string
|
||||
}
|
||||
data := []test{
|
||||
{"<h1>strip h1 tag <h1>", "strip h1 tag "},
|
||||
{"<p> strip p tag </p>", " strip p tag "},
|
||||
{"</br> strip br<br>", " strip br\n"},
|
||||
{"</br> strip br2<br />", " strip br2\n"},
|
||||
{"This <strong>is</strong> a\nnewline", "This is a newline"},
|
||||
{"No Tags", "No Tags"},
|
||||
{`<p>Summary Next Line.
|
||||
<figure >
|
||||
|
||||
<img src="/not/real" />
|
||||
|
||||
|
||||
</figure>
|
||||
.
|
||||
More text here.</p>
|
||||
|
||||
<p>Some more text</p>`, "Summary Next Line. . More text here.\nSome more text\n"},
|
||||
}
|
||||
for i, d := range data {
|
||||
output := StripHTML(d.input)
|
||||
if d.expected != output {
|
||||
t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkStripHTML(b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
StripHTML(tstHTMLContent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripEmptyNav(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
cleaned := stripEmptyNav([]byte("do<nav>\n</nav>\n\nbedobedo"))
|
||||
|
||||
+4
-21
@@ -1,11 +1,9 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/chroma/lexers"
|
||||
"github.com/alecthomas/chroma/v2/lexers"
|
||||
"github.com/gohugoio/hugo/docshelper"
|
||||
)
|
||||
|
||||
@@ -14,33 +12,18 @@ func init() {
|
||||
docsProvider := func() docshelper.DocProvider {
|
||||
var chromaLexers []any
|
||||
|
||||
sort.Sort(lexers.Registry.Lexers)
|
||||
sort.Sort(lexers.GlobalLexerRegistry.Lexers)
|
||||
|
||||
for _, l := range lexers.Registry.Lexers {
|
||||
for _, l := range lexers.GlobalLexerRegistry.Lexers {
|
||||
|
||||
config := l.Config()
|
||||
|
||||
var filenames []string
|
||||
filenames = append(filenames, config.Filenames...)
|
||||
filenames = append(filenames, config.AliasFilenames...)
|
||||
|
||||
aliases := config.Aliases
|
||||
|
||||
for _, filename := range filenames {
|
||||
alias := strings.TrimSpace(strings.TrimPrefix(filepath.Ext(filename), "."))
|
||||
if alias != "" {
|
||||
aliases = append(aliases, alias)
|
||||
}
|
||||
}
|
||||
|
||||
aliases = UniqueStringsSorted(aliases)
|
||||
|
||||
lexerEntry := struct {
|
||||
Name string
|
||||
Aliases []string
|
||||
}{
|
||||
config.Name,
|
||||
aliases,
|
||||
config.Aliases,
|
||||
}
|
||||
|
||||
chromaLexers = append(chromaLexers, lexerEntry)
|
||||
|
||||
+1
-9
@@ -33,8 +33,6 @@ import (
|
||||
|
||||
"github.com/mitchellh/hashstructure"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
|
||||
"github.com/gohugoio/hugo/common/hugo"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
@@ -521,13 +519,7 @@ func PrintFs(fs afero.Fs, path string, w io.Writer) {
|
||||
}
|
||||
|
||||
afero.Walk(fs, path, func(path string, info os.FileInfo, err error) error {
|
||||
var filename string
|
||||
var meta any
|
||||
if fim, ok := info.(hugofs.FileMetaInfo); ok {
|
||||
filename = fim.Meta().Filename
|
||||
meta = fim.Meta()
|
||||
}
|
||||
fmt.Fprintf(w, " %q %q\t\t%v\n", path, filename, meta)
|
||||
fmt.Println(path)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ func TestNewPathSpecFromConfig(t *testing.T) {
|
||||
v.Set("uglyURLs", true)
|
||||
v.Set("canonifyURLs", true)
|
||||
v.Set("paginatePath", "side")
|
||||
v.Set("baseURL", "http://base.com")
|
||||
v.Set("baseURL", "http://base.com/foo")
|
||||
v.Set("themesDir", "thethemes")
|
||||
v.Set("layoutDir", "thelayouts")
|
||||
v.Set("workingDir", "thework")
|
||||
@@ -53,7 +53,10 @@ func TestNewPathSpecFromConfig(t *testing.T) {
|
||||
c.Assert(p.Language.Lang, qt.Equals, "no")
|
||||
c.Assert(p.PaginatePath, qt.Equals, "side")
|
||||
|
||||
c.Assert(p.BaseURL.String(), qt.Equals, "http://base.com")
|
||||
c.Assert(p.BaseURL.String(), qt.Equals, "http://base.com/foo")
|
||||
c.Assert(p.BaseURLString, qt.Equals, "http://base.com/foo")
|
||||
c.Assert(p.BaseURLNoPathString, qt.Equals, "http://base.com")
|
||||
|
||||
c.Assert(p.ThemesDir, qt.Equals, "thethemes")
|
||||
c.Assert(p.WorkingDir, qt.Equals, "thework")
|
||||
}
|
||||
|
||||
+14
-11
@@ -103,17 +103,11 @@ func (p *PathSpec) AbsURL(in string, addLanguage bool) string {
|
||||
}
|
||||
|
||||
if url.IsAbs() || strings.HasPrefix(in, "//") {
|
||||
// It is already absolute, return it as is.
|
||||
return in
|
||||
}
|
||||
|
||||
var baseURL string
|
||||
if strings.HasPrefix(in, "/") {
|
||||
u := p.BaseURL.URL()
|
||||
u.Path = ""
|
||||
baseURL = u.String()
|
||||
} else {
|
||||
baseURL = p.BaseURL.String()
|
||||
}
|
||||
baseURL := p.getBaseURLRoot(in)
|
||||
|
||||
if addLanguage {
|
||||
prefix := p.GetLanguagePrefix()
|
||||
@@ -140,13 +134,22 @@ func (p *PathSpec) AbsURL(in string, addLanguage bool) string {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return paths.MakePermalink(baseURL, in).String()
|
||||
}
|
||||
|
||||
// RelURL creates a URL relative to the BaseURL root.
|
||||
// Note: The result URL will not include the context root if canonifyURLs is enabled.
|
||||
func (p *PathSpec) getBaseURLRoot(path string) string {
|
||||
if strings.HasPrefix(path, "/") {
|
||||
// Treat it as relative to the server root.
|
||||
return p.BaseURLNoPathString
|
||||
} else {
|
||||
// Treat it as relative to the baseURL.
|
||||
return p.BaseURLString
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PathSpec) RelURL(in string, addLanguage bool) string {
|
||||
baseURL := p.BaseURL.String()
|
||||
baseURL := p.getBaseURLRoot(in)
|
||||
canonifyURLs := p.CanonifyURLs
|
||||
if (!strings.HasPrefix(in, baseURL) && strings.HasPrefix(in, "http")) || strings.HasPrefix(in, "//") {
|
||||
return in
|
||||
|
||||
+17
-7
@@ -17,6 +17,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
"github.com/gohugoio/hugo/langs"
|
||||
)
|
||||
@@ -59,6 +60,7 @@ func TestAbsURL(t *testing.T) {
|
||||
}
|
||||
|
||||
func doTestAbsURL(t *testing.T, defaultInSubDir, addLanguage, multilingual bool, lang string) {
|
||||
c := qt.New(t)
|
||||
v := newTestCfg()
|
||||
v.Set("multilingual", multilingual)
|
||||
v.Set("defaultContentLanguage", "en")
|
||||
@@ -69,6 +71,10 @@ func doTestAbsURL(t *testing.T, defaultInSubDir, addLanguage, multilingual bool,
|
||||
baseURL string
|
||||
expected string
|
||||
}{
|
||||
// Issue 9994
|
||||
{"foo/bar", "https://example.org/foo/", "https://example.org/foo/MULTIfoo/bar"},
|
||||
{"/foo/bar", "https://example.org/foo/", "https://example.org/MULTIfoo/bar"},
|
||||
|
||||
{"/test/foo", "http://base/", "http://base/MULTItest/foo"},
|
||||
{"/" + lang + "/test/foo", "http://base/", "http://base/" + lang + "/test/foo"},
|
||||
{"", "http://base/ace/", "http://base/ace/MULTI"},
|
||||
@@ -113,9 +119,8 @@ func doTestAbsURL(t *testing.T, defaultInSubDir, addLanguage, multilingual bool,
|
||||
} else {
|
||||
expected = strings.Replace(expected, "MULTI", "", 1)
|
||||
}
|
||||
if output != expected {
|
||||
t.Fatalf("Expected %#v, got %#v\n", expected, output)
|
||||
}
|
||||
|
||||
c.Assert(output, qt.Equals, expected)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +137,7 @@ func TestRelURL(t *testing.T) {
|
||||
}
|
||||
|
||||
func doTestRelURL(t *testing.T, defaultInSubDir, addLanguage, multilingual bool, lang string) {
|
||||
c := qt.New(t)
|
||||
v := newTestCfg()
|
||||
v.Set("multilingual", multilingual)
|
||||
v.Set("defaultContentLanguage", "en")
|
||||
@@ -143,13 +149,18 @@ func doTestRelURL(t *testing.T, defaultInSubDir, addLanguage, multilingual bool,
|
||||
canonify bool
|
||||
expected string
|
||||
}{
|
||||
|
||||
// Issue 9994
|
||||
{"/foo/bar", "https://example.org/foo/", false, "MULTI/foo/bar"},
|
||||
{"foo/bar", "https://example.org/foo/", false, "/fooMULTI/foo/bar"},
|
||||
|
||||
{"/test/foo", "http://base/", false, "MULTI/test/foo"},
|
||||
{"/" + lang + "/test/foo", "http://base/", false, "/" + lang + "/test/foo"},
|
||||
{lang + "/test/foo", "http://base/", false, "/" + lang + "/test/foo"},
|
||||
{"test.css", "http://base/sub", false, "/subMULTI/test.css"},
|
||||
{"test.css", "http://base/sub", true, "MULTI/test.css"},
|
||||
{"/test/", "http://base/", false, "MULTI/test/"},
|
||||
{"/test/", "http://base/sub/", false, "/subMULTI/test/"},
|
||||
{"test/", "http://base/sub/", false, "/subMULTI/test/"},
|
||||
{"/test/", "http://base/sub/", true, "MULTI/test/"},
|
||||
{"", "http://base/ace/", false, "/aceMULTI/"},
|
||||
{"", "http://base/ace", false, "/aceMULTI"},
|
||||
@@ -189,9 +200,8 @@ func doTestRelURL(t *testing.T, defaultInSubDir, addLanguage, multilingual bool,
|
||||
expected = strings.Replace(expected, "MULTI", "", 1)
|
||||
}
|
||||
|
||||
if output != expected {
|
||||
t.Errorf("[%d][%t] Expected %#v, got %#v\n", i, test.canonify, expected, output)
|
||||
}
|
||||
c.Assert(output, qt.Equals, expected, qt.Commentf("[%d] %s", i, test.input))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
// Copyright 2016 The Hugo Authors. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package hugolib
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/hugofs"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
var (
|
||||
caseMixingSiteConfigTOML = `
|
||||
Title = "In an Insensitive Mood"
|
||||
DefaultContentLanguage = "nn"
|
||||
defaultContentLanguageInSubdir = true
|
||||
|
||||
[Blackfriday]
|
||||
AngledQuotes = true
|
||||
HrefTargetBlank = true
|
||||
|
||||
[Params]
|
||||
Search = true
|
||||
Color = "green"
|
||||
mood = "Happy"
|
||||
[Params.Colors]
|
||||
Blue = "blue"
|
||||
Yellow = "yellow"
|
||||
|
||||
[Languages]
|
||||
[Languages.nn]
|
||||
title = "Nynorsk title"
|
||||
languageName = "Nynorsk"
|
||||
weight = 1
|
||||
|
||||
[Languages.en]
|
||||
TITLE = "English title"
|
||||
LanguageName = "English"
|
||||
Mood = "Thoughtful"
|
||||
Weight = 2
|
||||
COLOR = "Pink"
|
||||
[Languages.en.blackfriday]
|
||||
angledQuotes = false
|
||||
hrefTargetBlank = false
|
||||
[Languages.en.Colors]
|
||||
BLUE = "blues"
|
||||
Yellow = "golden"
|
||||
`
|
||||
caseMixingPage1En = `
|
||||
---
|
||||
TITLE: Page1 En Translation
|
||||
BlackFriday:
|
||||
AngledQuotes: false
|
||||
Color: "black"
|
||||
Search: true
|
||||
mooD: "sad and lonely"
|
||||
ColorS:
|
||||
Blue: "bluesy"
|
||||
Yellow: "sunny"
|
||||
---
|
||||
# "Hi"
|
||||
{{< shortcode >}}
|
||||
`
|
||||
|
||||
caseMixingPage1 = `
|
||||
---
|
||||
titLe: Side 1
|
||||
blackFriday:
|
||||
angledQuotes: true
|
||||
color: "red"
|
||||
search: false
|
||||
MooD: "sad"
|
||||
COLORS:
|
||||
blue: "heavenly"
|
||||
yelloW: "Sunny"
|
||||
---
|
||||
# "Hi"
|
||||
{{< shortcode >}}
|
||||
`
|
||||
|
||||
caseMixingPage2 = `
|
||||
---
|
||||
TITLE: Page2 Title
|
||||
BlackFriday:
|
||||
AngledQuotes: false
|
||||
Color: "black"
|
||||
search: true
|
||||
MooD: "moody"
|
||||
ColorS:
|
||||
Blue: "sky"
|
||||
YELLOW: "flower"
|
||||
---
|
||||
# Hi
|
||||
{{< shortcode >}}
|
||||
`
|
||||
)
|
||||
|
||||
func caseMixingTestsWriteCommonSources(t *testing.T, fs afero.Fs) {
|
||||
writeToFs(t, fs, filepath.Join("content", "sect1", "page1.md"), caseMixingPage1)
|
||||
writeToFs(t, fs, filepath.Join("content", "sect2", "page2.md"), caseMixingPage2)
|
||||
writeToFs(t, fs, filepath.Join("content", "sect1", "page1.en.md"), caseMixingPage1En)
|
||||
|
||||
writeToFs(t, fs, "layouts/shortcodes/shortcode.html", `
|
||||
Shortcode Page: {{ .Page.Params.COLOR }}|{{ .Page.Params.Colors.Blue }}
|
||||
Shortcode Site: {{ .Page.Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW }}
|
||||
`)
|
||||
|
||||
writeToFs(t, fs, "layouts/partials/partial.html", `
|
||||
Partial Page: {{ .Params.COLOR }}|{{ .Params.Colors.Blue }}
|
||||
Partial Site: {{ .Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW }}
|
||||
Partial Site Global: {{ site.Params.COLOR }}|{{ site.Params.COLORS.YELLOW }}
|
||||
`)
|
||||
|
||||
writeToFs(t, fs, "config.toml", caseMixingSiteConfigTOML)
|
||||
}
|
||||
|
||||
func TestCaseInsensitiveConfigurationVariations(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
c := qt.New(t)
|
||||
|
||||
mm := afero.NewMemMapFs()
|
||||
|
||||
caseMixingTestsWriteCommonSources(t, mm)
|
||||
|
||||
cfg, _, err := LoadConfig(ConfigSourceDescriptor{Fs: mm, Filename: "config.toml"})
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
fs := hugofs.NewFrom(mm, cfg)
|
||||
|
||||
th := newTestHelper(cfg, fs, t)
|
||||
|
||||
writeSource(t, fs, filepath.Join("layouts", "_default", "baseof.html"), `
|
||||
Block Page Colors: {{ .Params.COLOR }}|{{ .Params.Colors.Blue }}
|
||||
{{ block "main" . }}default{{end}}`)
|
||||
|
||||
writeSource(t, fs, filepath.Join("layouts", "sect2", "single.html"), `
|
||||
{{ define "main"}}
|
||||
Page Colors: {{ .Params.CoLOR }}|{{ .Params.Colors.Blue }}
|
||||
Site Colors: {{ .Site.Params.COlOR }}|{{ .Site.Params.COLORS.YELLOW }}
|
||||
{{ template "index-color" (dict "name" "Page" "params" .Params) }}
|
||||
{{ template "index-color" (dict "name" "Site" "params" .Site.Params) }}
|
||||
|
||||
{{ .Content }}
|
||||
{{ partial "partial.html" . }}
|
||||
{{ end }}
|
||||
{{ define "index-color" }}
|
||||
{{ $yellow := index .params "COLoRS" "yELLOW" }}
|
||||
{{ $colors := index .params "COLoRS" }}
|
||||
{{ $yellow2 := index $colors "yEllow" }}
|
||||
index1|{{ .name }}: {{ $yellow }}|
|
||||
index2|{{ .name }}: {{ $yellow2 }}|
|
||||
{{ end }}
|
||||
`)
|
||||
|
||||
writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `
|
||||
Page Title: {{ .Title }}
|
||||
Site Title: {{ .Site.Title }}
|
||||
Site Lang Mood: {{ .Site.Language.Params.MOoD }}
|
||||
Page Colors: {{ .Params.COLOR }}|{{ .Params.Colors.Blue }}|{{ index .Params "ColOR" }}
|
||||
Site Colors: {{ .Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW }}|{{ index .Site.Params "ColOR" }}
|
||||
{{ $page2 := .Site.GetPage "/sect2/page2" }}
|
||||
{{ if $page2 }}
|
||||
Page2: {{ $page2.Params.ColoR }}
|
||||
{{ end }}
|
||||
{{ .Content }}
|
||||
{{ partial "partial.html" . }}
|
||||
`)
|
||||
|
||||
sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create sites: %s", err)
|
||||
}
|
||||
|
||||
err = sites.Build(BuildCfg{})
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to build sites: %s", err)
|
||||
}
|
||||
|
||||
th.assertFileContent(filepath.Join("public", "nn", "sect1", "page1", "index.html"),
|
||||
"Page Colors: red|heavenly|red",
|
||||
"Site Colors: green|yellow|green",
|
||||
"Site Lang Mood: Happy",
|
||||
"Shortcode Page: red|heavenly",
|
||||
"Shortcode Site: green|yellow",
|
||||
"Partial Page: red|heavenly",
|
||||
"Partial Site: green|yellow",
|
||||
"Partial Site Global: green|yellow",
|
||||
"Page Title: Side 1",
|
||||
"Site Title: Nynorsk title",
|
||||
"Page2: black ",
|
||||
)
|
||||
|
||||
th.assertFileContent(filepath.Join("public", "en", "sect1", "page1", "index.html"),
|
||||
"Site Colors: Pink|golden",
|
||||
"Page Colors: black|bluesy",
|
||||
"Site Lang Mood: Thoughtful",
|
||||
"Page Title: Page1 En Translation",
|
||||
"Site Title: English title",
|
||||
"“Hi”",
|
||||
)
|
||||
|
||||
th.assertFileContent(filepath.Join("public", "nn", "sect2", "page2", "index.html"),
|
||||
"Page Colors: black|sky",
|
||||
"Site Colors: green|yellow",
|
||||
"Shortcode Page: black|sky",
|
||||
"Block Page Colors: black|sky",
|
||||
"Partial Page: black|sky",
|
||||
"Partial Site: green|yellow",
|
||||
"index1|Page: flower|",
|
||||
"index1|Site: yellow|",
|
||||
"index2|Page: flower|",
|
||||
"index2|Site: yellow|",
|
||||
)
|
||||
}
|
||||
@@ -108,11 +108,6 @@ func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provid
|
||||
}
|
||||
}
|
||||
|
||||
// Config deprecations.
|
||||
if l.cfg.GetString("markup.defaultMarkdownHandler") == "blackfriday" {
|
||||
helpers.Deprecated("markup.defaultMarkdownHandler=blackfriday", "See https://gohugo.io//content-management/formats/#list-of-content-formats", false)
|
||||
}
|
||||
|
||||
// Some settings are used before we're done collecting all settings,
|
||||
// so apply OS environment both before and after.
|
||||
if err := l.applyOsEnvOverrides(d.Environ); err != nil {
|
||||
|
||||
@@ -756,3 +756,29 @@ theme_param="themevalue2"
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestInvalidDefaultMarkdownHandler(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- config.toml --
|
||||
[markup]
|
||||
defaultMarkdownHandler = 'blackfriday'
|
||||
-- content/_index.md --
|
||||
## Foo
|
||||
-- layouts/index.html --
|
||||
{{ .Content }}
|
||||
|
||||
`
|
||||
|
||||
b, err := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).BuildE()
|
||||
|
||||
b.Assert(err, qt.IsNotNil)
|
||||
b.Assert(err.Error(), qt.Contains, "Configured defaultMarkdownHandler \"blackfriday\" not found. Did you mean to use goldmark? Blackfriday was removed in Hugo v0.100.0.")
|
||||
|
||||
}
|
||||
|
||||
@@ -163,8 +163,6 @@ func (m *pageMap) newPageFromContentNode(n *contentNode, parentBucket *pagesMapB
|
||||
},
|
||||
}
|
||||
|
||||
ps.shortcodeState = newShortcodeHandler(ps, ps.s, nil)
|
||||
|
||||
if err := ps.mapContent(parentBucket, metaProvider); err != nil {
|
||||
return nil, ps.wrapError(err)
|
||||
}
|
||||
|
||||
@@ -383,8 +383,8 @@ Blog Section: {{ template "print-page" $blog }}
|
||||
Blog Sub Section: {{ template "print-page" $blogSub }}
|
||||
Page: {{ template "print-page" $page }}
|
||||
Bundle: {{ template "print-page" $bundle }}
|
||||
IsDescendant: true: {{ $page.IsDescendant $blog }} true: {{ $blogSub.IsDescendant $blog }} true: {{ $bundle.IsDescendant $blog }} true: {{ $page4.IsDescendant $blog }} true: {{ $blog.IsDescendant $home }} true: {{ $blog.IsDescendant $blog }} false: {{ $home.IsDescendant $blog }}
|
||||
IsAncestor: true: {{ $blog.IsAncestor $page }} true: {{ $home.IsAncestor $blog }} true: {{ $blog.IsAncestor $blogSub }} true: {{ $blog.IsAncestor $bundle }} true: {{ $blog.IsAncestor $page4 }} true: {{ $home.IsAncestor $page }} true: {{ $blog.IsAncestor $blog }} false: {{ $page.IsAncestor $blog }} false: {{ $blog.IsAncestor $home }} false: {{ $blogSub.IsAncestor $blog }}
|
||||
IsDescendant: true: {{ $page.IsDescendant $blog }} true: {{ $blogSub.IsDescendant $blog }} true: {{ $bundle.IsDescendant $blog }} true: {{ $page4.IsDescendant $blog }} true: {{ $blog.IsDescendant $home }} false: {{ $blog.IsDescendant $blog }} false: {{ $home.IsDescendant $blog }}
|
||||
IsAncestor: true: {{ $blog.IsAncestor $page }} true: {{ $home.IsAncestor $blog }} true: {{ $blog.IsAncestor $blogSub }} true: {{ $blog.IsAncestor $bundle }} true: {{ $blog.IsAncestor $page4 }} true: {{ $home.IsAncestor $page }} false: {{ $blog.IsAncestor $blog }} false: {{ $page.IsAncestor $blog }} false: {{ $blog.IsAncestor $home }} false: {{ $blogSub.IsAncestor $blog }}
|
||||
IsDescendant overlap1: false: {{ $overlap1.IsDescendant $overlap2 }}
|
||||
IsDescendant overlap2: false: {{ $overlap2.IsDescendant $overlap1 }}
|
||||
IsAncestor overlap1: false: {{ $overlap1.IsAncestor $overlap2 }}
|
||||
@@ -426,8 +426,8 @@ Draft5: {{ if (.Site.GetPage "blog/draftsection/sub/page") }}FOUND{{ end }}|
|
||||
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:
|
||||
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 true: true false: false
|
||||
IsAncestor: true: true true: true true: true true: true true: true true: true true: true false: false false: false false: false
|
||||
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
|
||||
IsDescendant overlap1: false: false
|
||||
IsDescendant overlap2: false: false
|
||||
IsAncestor overlap1: false: false
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
)
|
||||
|
||||
func TestRenderHookEditNestedPartial(t *testing.T) {
|
||||
@@ -428,72 +427,3 @@ Image:
|
||||
<p>html-image: image.jpg|Text: Hello<br> Goodbye|Plain: Hello GoodbyeEND</p>
|
||||
`)
|
||||
}
|
||||
|
||||
func TestRenderString(t *testing.T) {
|
||||
b := newTestSitesBuilder(t)
|
||||
|
||||
b.WithTemplates("index.html", `
|
||||
{{ $p := site.GetPage "p1.md" }}
|
||||
{{ $optBlock := dict "display" "block" }}
|
||||
{{ $optOrg := dict "markup" "org" }}
|
||||
RSTART:{{ "**Bold Markdown**" | $p.RenderString }}:REND
|
||||
RSTART:{{ "**Bold Block Markdown**" | $p.RenderString $optBlock }}:REND
|
||||
RSTART:{{ "/italic org mode/" | $p.RenderString $optOrg }}:REND
|
||||
RSTART:{{ "## Header2" | $p.RenderString }}:REND
|
||||
|
||||
|
||||
`, "_default/_markup/render-heading.html", "Hook Heading: {{ .Level }}")
|
||||
|
||||
b.WithContent("p1.md", `---
|
||||
title: "p1"
|
||||
---
|
||||
`,
|
||||
)
|
||||
|
||||
b.Build(BuildCfg{})
|
||||
|
||||
b.AssertFileContent("public/index.html", `
|
||||
RSTART:<strong>Bold Markdown</strong>:REND
|
||||
RSTART:<p><strong>Bold Block Markdown</strong></p>
|
||||
RSTART:<em>italic org mode</em>:REND
|
||||
RSTART:Hook Heading: 2:REND
|
||||
`)
|
||||
}
|
||||
|
||||
// https://github.com/gohugoio/hugo/issues/6882
|
||||
func TestRenderStringOnListPage(t *testing.T) {
|
||||
renderStringTempl := `
|
||||
{{ .RenderString "**Hello**" }}
|
||||
`
|
||||
b := newTestSitesBuilder(t)
|
||||
b.WithContent("mysection/p1.md", `FOO`)
|
||||
b.WithTemplates(
|
||||
"index.html", renderStringTempl,
|
||||
"_default/list.html", renderStringTempl,
|
||||
"_default/single.html", renderStringTempl,
|
||||
)
|
||||
|
||||
b.Build(BuildCfg{})
|
||||
|
||||
for _, filename := range []string{
|
||||
"index.html",
|
||||
"mysection/index.html",
|
||||
"categories/index.html",
|
||||
"tags/index.html",
|
||||
"mysection/p1/index.html",
|
||||
} {
|
||||
b.AssertFileContent("public/"+filename, `<strong>Hello</strong>`)
|
||||
}
|
||||
}
|
||||
|
||||
// Issue 9433
|
||||
func TestRenderStringOnPageNotBackedByAFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
logger := loggers.NewWarningLogger()
|
||||
b := newTestSitesBuilder(t).WithLogger(logger).WithConfigFile("toml", `
|
||||
disableKinds = ["page", "section", "taxonomy", "term"]
|
||||
`)
|
||||
b.WithTemplates("index.html", `{{ .RenderString "**Hello**" }}`).WithContent("p1.md", "")
|
||||
b.BuildE(BuildCfg{})
|
||||
b.Assert(int(logger.LogCounters().WarnCounter.Count()), qt.Equals, 0)
|
||||
}
|
||||
|
||||
@@ -214,3 +214,62 @@ func TestTimeOnError(t *testing.T) {
|
||||
b.Assert(b.BuildE(BuildCfg{}), qt.Not(qt.IsNil))
|
||||
|
||||
}
|
||||
|
||||
func TestTOMLDates(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- config.toml --
|
||||
timeZone = "America/Los_Angeles"
|
||||
-- content/_index.md --
|
||||
---
|
||||
date: "2020-10-20"
|
||||
---
|
||||
-- content/p1.md --
|
||||
+++
|
||||
title = "TOML Date with UTC offset"
|
||||
date = 2021-08-16T06:00:00+00:00
|
||||
+++
|
||||
|
||||
|
||||
## Foo
|
||||
-- data/mydata.toml --
|
||||
date = 2020-10-20
|
||||
talks = [
|
||||
{ date = 2017-01-23, name = "Past talk 1" },
|
||||
{ date = 2017-01-24, name = "Past talk 2" },
|
||||
{ date = 2017-01-26, name = "Past talk 3" },
|
||||
{ date = 2050-02-12, name = "Future talk 1" },
|
||||
{ date = 2050-02-13, name = "Future talk 2" },
|
||||
]
|
||||
-- layouts/index.html --
|
||||
{{ $futureTalks := where site.Data.mydata.talks "date" ">" now }}
|
||||
{{ $pastTalks := where site.Data.mydata.talks "date" "<" now }}
|
||||
|
||||
{{ $homeDate := site.Home.Date }}
|
||||
{{ $p1Date := (site.GetPage "p1").Date }}
|
||||
Future talks: {{ len $futureTalks }}
|
||||
Past talks: {{ len $pastTalks }}
|
||||
|
||||
Home's Date should be greater than past: {{ gt $homeDate (index $pastTalks 0).date }}
|
||||
Home's Date should be less than future: {{ lt $homeDate (index $futureTalks 0).date }}
|
||||
Home's Date should be equal mydata date: {{ eq $homeDate site.Data.mydata.date }}
|
||||
Full time: {{ $p1Date | time.Format ":time_full" }}
|
||||
`
|
||||
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/index.html", `
|
||||
Future talks: 2
|
||||
Past talks: 3
|
||||
Home's Date should be greater than past: true
|
||||
Home's Date should be less than future: true
|
||||
Home's Date should be equal mydata date: true
|
||||
Full time: 6:00:00 am UTC
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -76,18 +76,24 @@ type BaseFs struct {
|
||||
theBigFs *filesystemsCollector
|
||||
|
||||
// Locks.
|
||||
buildMu *lockedfile.Mutex // <project>/.hugo_build.lock
|
||||
buildMuTests sync.Mutex // Used in tests.
|
||||
buildMu Lockable // <project>/.hugo_build.lock
|
||||
}
|
||||
|
||||
type Lockable interface {
|
||||
Lock() (unlock func(), err error)
|
||||
}
|
||||
|
||||
type fakeLockfileMutex struct {
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (f *fakeLockfileMutex) Lock() (func(), error) {
|
||||
f.mu.Lock()
|
||||
return func() { f.mu.Unlock() }, nil
|
||||
}
|
||||
|
||||
// Tries to acquire a build lock.
|
||||
func (fs *BaseFs) LockBuild() (unlock func(), err error) {
|
||||
if htesting.IsTest {
|
||||
fs.buildMuTests.Lock()
|
||||
return func() {
|
||||
fs.buildMuTests.Unlock()
|
||||
}, nil
|
||||
}
|
||||
return fs.buildMu.Lock()
|
||||
}
|
||||
|
||||
@@ -445,12 +451,19 @@ func NewBase(p *paths.Paths, logger loggers.Logger, options ...func(*BaseFs) err
|
||||
sourceFs := hugofs.NewBaseFileDecorator(afero.NewBasePathFs(fs.Source, p.WorkingDir))
|
||||
publishFsStatic := fs.PublishDirStatic
|
||||
|
||||
var buildMu Lockable
|
||||
if p.Cfg.GetBool("noBuildLock") || htesting.IsTest {
|
||||
buildMu = &fakeLockfileMutex{}
|
||||
} else {
|
||||
buildMu = lockedfile.MutexAt(filepath.Join(p.WorkingDir, lockFileBuild))
|
||||
}
|
||||
|
||||
b := &BaseFs{
|
||||
SourceFs: sourceFs,
|
||||
WorkDir: fs.WorkingDirReadOnly,
|
||||
PublishFs: publishFs,
|
||||
PublishFsStatic: publishFsStatic,
|
||||
buildMu: lockedfile.MutexAt(filepath.Join(p.WorkingDir, lockFileBuild)),
|
||||
buildMu: buildMu,
|
||||
}
|
||||
|
||||
for _, opt := range options {
|
||||
|
||||
@@ -365,11 +365,6 @@ func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {
|
||||
c.Assert(frTags["FRtag1"], qt.Not(qt.IsNil))
|
||||
b.AssertFileContent("public/fr/plaques/FRtag1/index.html", "FRtag1|Bonjour|http://example.com/blog/fr/plaques/FRtag1/")
|
||||
|
||||
// Check Blackfriday config
|
||||
c.Assert(strings.Contains(content(doc1fr), "«"), qt.Equals, true)
|
||||
c.Assert(strings.Contains(content(doc1en), "«"), qt.Equals, false)
|
||||
c.Assert(strings.Contains(content(doc1en), "“"), qt.Equals, true)
|
||||
|
||||
// en and nn have custom site menus
|
||||
c.Assert(len(frSite.Menus()), qt.Equals, 0)
|
||||
c.Assert(len(enSite.Menus()), qt.Equals, 1)
|
||||
@@ -796,39 +791,6 @@ categories: ["mycat"]
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/gohugoio/hugo/issues/5777
|
||||
func TestTableOfContentsInShortcodes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
b := newMultiSiteTestDefaultBuilder(t)
|
||||
|
||||
b.WithTemplatesAdded("layouts/shortcodes/toc.html", tocShortcode)
|
||||
b.WithTemplatesAdded("layouts/shortcodes/wrapper.html", "{{ .Inner }}")
|
||||
b.WithContent("post/simple.en.md", tocPageSimple)
|
||||
b.WithContent("post/variants1.en.md", tocPageVariants1)
|
||||
b.WithContent("post/variants2.en.md", tocPageVariants2)
|
||||
|
||||
b.WithContent("post/withSCInHeading.en.md", tocPageWithShortcodesInHeadings)
|
||||
|
||||
b.CreateSites().Build(BuildCfg{})
|
||||
|
||||
b.AssertFileContent("public/en/post/simple/index.html",
|
||||
tocPageSimpleExpected,
|
||||
// Make sure it is inserted twice
|
||||
`TOC1: <nav id="TableOfContents">`,
|
||||
`TOC2: <nav id="TableOfContents">`,
|
||||
)
|
||||
|
||||
b.AssertFileContentFn("public/en/post/variants1/index.html", func(s string) bool {
|
||||
return strings.Count(s, "TableOfContents") == 4
|
||||
})
|
||||
b.AssertFileContentFn("public/en/post/variants2/index.html", func(s string) bool {
|
||||
return strings.Count(s, "TableOfContents") == 6
|
||||
})
|
||||
|
||||
b.AssertFileContent("public/en/post/withSCInHeading/index.html", tocPageWithShortcodesInHeadingsExpected)
|
||||
}
|
||||
|
||||
var tocShortcode = `
|
||||
TOC1: {{ .Page.TableOfContents }}
|
||||
|
||||
@@ -970,12 +932,6 @@ enableRobotsTXT = true
|
||||
[permalinks]
|
||||
other = "/somewhere/else/:filename"
|
||||
|
||||
# TODO(bep)
|
||||
[markup]
|
||||
defaultMarkdownHandler = "blackfriday"
|
||||
[markup.blackfriday]
|
||||
angledQuotes = true
|
||||
|
||||
[Taxonomies]
|
||||
tag = "tags"
|
||||
|
||||
@@ -984,8 +940,6 @@ tag = "tags"
|
||||
weight = 10
|
||||
title = "In English"
|
||||
languageName = "English"
|
||||
[Languages.en.blackfriday]
|
||||
angledQuotes = false
|
||||
[[Languages.en.menu.main]]
|
||||
url = "/"
|
||||
name = "Home"
|
||||
@@ -1031,12 +985,6 @@ enableRobotsTXT: true
|
||||
permalinks:
|
||||
other: "/somewhere/else/:filename"
|
||||
|
||||
# TODO(bep)
|
||||
markup:
|
||||
defaultMarkdownHandler: blackfriday
|
||||
blackFriday:
|
||||
angledQuotes: true
|
||||
|
||||
Taxonomies:
|
||||
tag: "tags"
|
||||
|
||||
@@ -1045,8 +993,6 @@ Languages:
|
||||
weight: 10
|
||||
title: "In English"
|
||||
languageName: "English"
|
||||
blackfriday:
|
||||
angledQuotes: false
|
||||
menu:
|
||||
main:
|
||||
- url: "/"
|
||||
@@ -1092,12 +1038,6 @@ var multiSiteJSONConfigTemplate = `
|
||||
"permalinks": {
|
||||
"other": "/somewhere/else/:filename"
|
||||
},
|
||||
"markup": {
|
||||
"defaultMarkdownHandler": "blackfriday",
|
||||
"blackfriday": {
|
||||
"angledQuotes": true
|
||||
}
|
||||
},
|
||||
"Taxonomies": {
|
||||
"tag": "tags"
|
||||
},
|
||||
@@ -1106,9 +1046,6 @@ var multiSiteJSONConfigTemplate = `
|
||||
"weight": 10,
|
||||
"title": "In English",
|
||||
"languageName": "English",
|
||||
"blackfriday": {
|
||||
"angledQuotes": false
|
||||
},
|
||||
"menu": {
|
||||
"main": [
|
||||
{
|
||||
|
||||
@@ -15,6 +15,7 @@ package hugolib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -320,3 +321,130 @@ The content.
|
||||
|
||||
b.CreateSites().Build(BuildCfg{})
|
||||
}
|
||||
|
||||
// This is just a test to verify that BenchmarkBaseline is working as intended.
|
||||
func TestBenchmarkBaseline(t *testing.T) {
|
||||
cfg := IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: benchmarkBaselineFiles(),
|
||||
}
|
||||
b := NewIntegrationTestBuilder(cfg).Build()
|
||||
|
||||
b.Assert(len(b.H.Sites), qt.Equals, 4)
|
||||
b.Assert(len(b.H.Sites[0].RegularPages()), qt.Equals, 161)
|
||||
b.Assert(len(b.H.Sites[0].Pages()), qt.Equals, 197)
|
||||
b.Assert(len(b.H.Sites[2].RegularPages()), qt.Equals, 158)
|
||||
b.Assert(len(b.H.Sites[2].Pages()), qt.Equals, 194)
|
||||
|
||||
}
|
||||
|
||||
func BenchmarkBaseline(b *testing.B) {
|
||||
cfg := IntegrationTestConfig{
|
||||
T: b,
|
||||
TxtarString: benchmarkBaselineFiles(),
|
||||
}
|
||||
builders := make([]*IntegrationTestBuilder, b.N)
|
||||
|
||||
for i := range builders {
|
||||
builders[i] = NewIntegrationTestBuilder(cfg)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
builders[i].Build()
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkBaselineFiles() string {
|
||||
|
||||
rnd := rand.New(rand.NewSource(32))
|
||||
|
||||
files := `
|
||||
-- config.toml --
|
||||
baseURL = "https://example.com"
|
||||
defaultContentLanguage = 'en'
|
||||
|
||||
[module]
|
||||
[[module.mounts]]
|
||||
source = 'content/en'
|
||||
target = 'content/en'
|
||||
lang = 'en'
|
||||
[[module.mounts]]
|
||||
source = 'content/nn'
|
||||
target = 'content/nn'
|
||||
lang = 'nn'
|
||||
[[module.mounts]]
|
||||
source = 'content/no'
|
||||
target = 'content/no'
|
||||
lang = 'no'
|
||||
[[module.mounts]]
|
||||
source = 'content/sv'
|
||||
target = 'content/sv'
|
||||
lang = 'sv'
|
||||
[[module.mounts]]
|
||||
source = 'layouts'
|
||||
target = 'layouts'
|
||||
|
||||
[languages]
|
||||
[languages.en]
|
||||
title = "English"
|
||||
weight = 1
|
||||
[languages.nn]
|
||||
title = "Nynorsk"
|
||||
weight = 2
|
||||
[languages.no]
|
||||
title = "Norsk"
|
||||
weight = 3
|
||||
[languages.sv]
|
||||
title = "Svenska"
|
||||
weight = 4
|
||||
-- layouts/_default/list.html --
|
||||
{{ .Title }}
|
||||
{{ .Content }}
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Title }}
|
||||
{{ .Content }}
|
||||
-- layouts/shortcodes/myshort.html --
|
||||
{{ .Inner }}
|
||||
`
|
||||
|
||||
contentTemplate := `
|
||||
---
|
||||
title: "Page %d"
|
||||
date: "2018-01-01"
|
||||
weight: %d
|
||||
---
|
||||
|
||||
## Heading 1
|
||||
|
||||
Duis nisi reprehenderit nisi cupidatat cillum aliquip ea id eu esse commodo et.
|
||||
|
||||
## Heading 2
|
||||
|
||||
Aliqua labore enim et sint anim amet excepteur ea dolore.
|
||||
|
||||
{{< myshort >}}
|
||||
Hello, World!
|
||||
{{< /myshort >}}
|
||||
|
||||
Aliqua labore enim et sint anim amet excepteur ea dolore.
|
||||
|
||||
|
||||
`
|
||||
|
||||
for _, lang := range []string{"en", "nn", "no", "sv"} {
|
||||
files += fmt.Sprintf("\n-- content/%s/_index.md --\n"+contentTemplate, lang, 1, 1, 1)
|
||||
for i, root := range []string{"", "foo", "bar", "baz"} {
|
||||
for j, section := range []string{"posts", "posts/funny", "posts/science", "posts/politics", "posts/world", "posts/technology", "posts/world/news", "posts/world/news/europe"} {
|
||||
n := i + j + 1
|
||||
files += fmt.Sprintf("\n-- content/%s/%s/%s/_index.md --\n"+contentTemplate, lang, root, section, n, n, n)
|
||||
for k := 1; k < rnd.Intn(30)+1; k++ {
|
||||
n := n + k
|
||||
files += fmt.Sprintf("\n-- content/%s/%s/%s/p%d.md --\n"+contentTemplate, lang, root, section, n, n, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
@@ -41,13 +41,15 @@ func NewIntegrationTestBuilder(conf IntegrationTestConfig) *IntegrationTestBuild
|
||||
}
|
||||
|
||||
if conf.NeedsOsFS {
|
||||
tempDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-integration-test")
|
||||
c.Assert(err, qt.IsNil)
|
||||
conf.WorkingDir = filepath.Join(tempDir, conf.WorkingDir)
|
||||
if !conf.PrintAndKeepTempDir {
|
||||
c.Cleanup(clean)
|
||||
} else {
|
||||
fmt.Println("\nUsing WorkingDir dir:", conf.WorkingDir)
|
||||
if !filepath.IsAbs(conf.WorkingDir) {
|
||||
tempDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-integration-test")
|
||||
c.Assert(err, qt.IsNil)
|
||||
conf.WorkingDir = filepath.Join(tempDir, conf.WorkingDir)
|
||||
if !conf.PrintAndKeepTempDir {
|
||||
c.Cleanup(clean)
|
||||
} else {
|
||||
fmt.Println("\nUsing WorkingDir dir:", conf.WorkingDir)
|
||||
}
|
||||
}
|
||||
} else if conf.WorkingDir == "" {
|
||||
conf.WorkingDir = helpers.FilePathSeparator
|
||||
@@ -195,7 +197,10 @@ func (s *IntegrationTestBuilder) Build() *IntegrationTestBuilder {
|
||||
|
||||
func (s *IntegrationTestBuilder) BuildE() (*IntegrationTestBuilder, error) {
|
||||
s.Helper()
|
||||
s.initBuilder()
|
||||
if err := s.initBuilder(); err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
err := s.build(BuildCfg{})
|
||||
return s, err
|
||||
}
|
||||
@@ -265,7 +270,8 @@ func (s *IntegrationTestBuilder) FileContent(filename string) string {
|
||||
return s.readWorkingDir(s, s.fs, filepath.FromSlash(filename))
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) initBuilder() {
|
||||
func (s *IntegrationTestBuilder) initBuilder() error {
|
||||
var initErr error
|
||||
s.builderInit.Do(func() {
|
||||
var afs afero.Fs
|
||||
if s.Cfg.NeedsOsFS {
|
||||
@@ -318,7 +324,10 @@ func (s *IntegrationTestBuilder) initBuilder() {
|
||||
|
||||
depsCfg := deps.DepsCfg{Cfg: cfg, Fs: fs, Running: s.Cfg.Running, Logger: logger}
|
||||
sites, err := NewHugoSites(depsCfg)
|
||||
s.Assert(err, qt.IsNil)
|
||||
if err != nil {
|
||||
initErr = err
|
||||
return
|
||||
}
|
||||
|
||||
s.H = sites
|
||||
s.fs = fs
|
||||
@@ -336,6 +345,8 @@ func (s *IntegrationTestBuilder) initBuilder() {
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
return initErr
|
||||
}
|
||||
|
||||
func (s *IntegrationTestBuilder) absFilename(filename string) string {
|
||||
|
||||
@@ -524,3 +524,67 @@ Blog|HasMenuCurrent: false|Page: Page(/blog/_index.md)
|
||||
Blog|IsMenuCurrent: false|Page: Page(/blog/_index.md)
|
||||
`)
|
||||
}
|
||||
|
||||
// Issue 9846
|
||||
func TestMenuHasMenuCurrentSection(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- config.toml --
|
||||
disableKinds = ['RSS','sitemap','taxonomy','term']
|
||||
[[menu.main]]
|
||||
name = 'Home'
|
||||
pageRef = '/'
|
||||
weight = 1
|
||||
|
||||
[[menu.main]]
|
||||
name = 'Tests'
|
||||
pageRef = '/tests'
|
||||
weight = 2
|
||||
[[menu.main]]
|
||||
name = 'Test 1'
|
||||
pageRef = '/tests/test-1'
|
||||
parent = 'Tests'
|
||||
weight = 1
|
||||
|
||||
-- content/tests/test-1.md --
|
||||
---
|
||||
title: "Test 1"
|
||||
---
|
||||
-- layouts/_default/list.html --
|
||||
{{ range site.Menus.main }}
|
||||
{{ .Name }}|{{ .URL }}|IsMenuCurrent = {{ $.IsMenuCurrent "main" . }}|HasMenuCurrent = {{ $.HasMenuCurrent "main" . }}|
|
||||
{{ range .Children }}
|
||||
{{ .Name }}|{{ .URL }}|IsMenuCurrent = {{ $.IsMenuCurrent "main" . }}|HasMenuCurrent = {{ $.HasMenuCurrent "main" . }}|
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{/* Some tests for issue 9925 */}}
|
||||
{{ $page := .Site.GetPage "tests/test-1" }}
|
||||
{{ $section := site.GetPage "tests" }}
|
||||
|
||||
Home IsAncestor Self: {{ site.Home.IsAncestor site.Home }}
|
||||
Home IsDescendant Self: {{ site.Home.IsDescendant site.Home }}
|
||||
Section IsAncestor Self: {{ $section.IsAncestor $section }}
|
||||
Section IsDescendant Self: {{ $section.IsDescendant $section}}
|
||||
Page IsAncestor Self: {{ $page.IsAncestor $page }}
|
||||
Page IsDescendant Self: {{ $page.IsDescendant $page}}
|
||||
`
|
||||
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/tests/index.html", `
|
||||
Tests|/tests/|IsMenuCurrent = true|HasMenuCurrent = false
|
||||
Home IsAncestor Self: false
|
||||
Home IsDescendant Self: false
|
||||
Section IsAncestor Self: false
|
||||
Section IsDescendant Self: false
|
||||
Page IsAncestor Self: false
|
||||
Page IsDescendant Self: false
|
||||
`)
|
||||
}
|
||||
|
||||
+44
-27
@@ -336,7 +336,7 @@ func (p *pageState) HasShortcode(name string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
return p.shortcodeState.nameSet[name]
|
||||
return p.shortcodeState.hasName(name)
|
||||
}
|
||||
|
||||
func (p *pageState) Site() page.Site {
|
||||
@@ -600,7 +600,7 @@ func (p *pageState) getContentConverter() converter.Converter {
|
||||
// Only used for shortcode inner content.
|
||||
markup = "markdown"
|
||||
}
|
||||
p.m.contentConverter, err = p.m.newContentConverter(p, markup, p.m.renderingConfigOverrides)
|
||||
p.m.contentConverter, err = p.m.newContentConverter(p, markup)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -610,19 +610,36 @@ func (p *pageState) getContentConverter() converter.Converter {
|
||||
}
|
||||
|
||||
func (p *pageState) mapContent(bucket *pagesMapBucket, meta *pageMeta) error {
|
||||
s := p.shortcodeState
|
||||
|
||||
rn := &pageContentMap{
|
||||
p.cmap = &pageContentMap{
|
||||
items: make([]any, 0, 20),
|
||||
}
|
||||
|
||||
iter := p.source.parsed.Iterator()
|
||||
return p.mapContentForResult(
|
||||
p.source.parsed,
|
||||
p.shortcodeState,
|
||||
p.cmap,
|
||||
meta.markup,
|
||||
func(m map[string]interface{}) error {
|
||||
return meta.setMetadata(bucket, p, m)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (p *pageState) mapContentForResult(
|
||||
result pageparser.Result,
|
||||
s *shortcodeHandler,
|
||||
rn *pageContentMap,
|
||||
markup string,
|
||||
withFrontMatter func(map[string]any) error,
|
||||
) error {
|
||||
|
||||
iter := result.Iterator()
|
||||
|
||||
fail := func(err error, i pageparser.Item) error {
|
||||
if fe, ok := err.(herrors.FileError); ok {
|
||||
return fe
|
||||
}
|
||||
return p.parseError(err, iter.Input(), i.Pos)
|
||||
return p.parseError(err, result.Input(), i.Pos())
|
||||
}
|
||||
|
||||
// the parser is guaranteed to return items in proper order or fail, so …
|
||||
@@ -639,14 +656,14 @@ Loop:
|
||||
case it.Type == pageparser.TypeIgnore:
|
||||
case it.IsFrontMatter():
|
||||
f := pageparser.FormatFromFrontMatterType(it.Type)
|
||||
m, err := metadecoders.Default.UnmarshalToMap(it.Val, f)
|
||||
m, err := metadecoders.Default.UnmarshalToMap(it.Val(result.Input()), f)
|
||||
if err != nil {
|
||||
if fe, ok := err.(herrors.FileError); ok {
|
||||
pos := fe.Position()
|
||||
// Apply the error to the content file.
|
||||
pos.Filename = p.File().Filename()
|
||||
// Offset the starting position of front matter.
|
||||
offset := iter.LineNumber() - 1
|
||||
offset := iter.LineNumber(result.Input()) - 1
|
||||
if f == metadecoders.YAML {
|
||||
offset -= 1
|
||||
}
|
||||
@@ -660,15 +677,17 @@ Loop:
|
||||
}
|
||||
}
|
||||
|
||||
if err := meta.setMetadata(bucket, p, m); err != nil {
|
||||
return err
|
||||
if withFrontMatter != nil {
|
||||
if err := withFrontMatter(m); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
frontMatterSet = true
|
||||
|
||||
next := iter.Peek()
|
||||
if !next.IsDone() {
|
||||
p.source.posMainContent = next.Pos
|
||||
p.source.posMainContent = next.Pos()
|
||||
}
|
||||
|
||||
if !p.s.shouldBuild(p) {
|
||||
@@ -680,10 +699,10 @@ Loop:
|
||||
posBody := -1
|
||||
f := func(item pageparser.Item) bool {
|
||||
if posBody == -1 && !item.IsDone() {
|
||||
posBody = item.Pos
|
||||
posBody = item.Pos()
|
||||
}
|
||||
|
||||
if item.IsNonWhitespace() {
|
||||
if item.IsNonWhitespace(result.Input()) {
|
||||
p.truncated = true
|
||||
|
||||
// Done
|
||||
@@ -693,12 +712,12 @@ Loop:
|
||||
}
|
||||
iter.PeekWalk(f)
|
||||
|
||||
p.source.posSummaryEnd = it.Pos
|
||||
p.source.posSummaryEnd = it.Pos()
|
||||
p.source.posBodyStart = posBody
|
||||
p.source.hasSummaryDivider = true
|
||||
|
||||
if meta.markup != "html" {
|
||||
// The content will be rendered by Blackfriday or similar,
|
||||
if markup != "html" {
|
||||
// The content will be rendered by Goldmark or similar,
|
||||
// and we need to track the summary.
|
||||
rn.AddReplacement(internalSummaryDividerPre, it)
|
||||
}
|
||||
@@ -708,19 +727,19 @@ Loop:
|
||||
// let extractShortcode handle left delim (will do so recursively)
|
||||
iter.Backup()
|
||||
|
||||
currShortcode, err := s.extractShortcode(ordinal, 0, iter)
|
||||
currShortcode, err := s.extractShortcode(ordinal, 0, result.Input(), iter)
|
||||
if err != nil {
|
||||
return fail(err, it)
|
||||
}
|
||||
|
||||
currShortcode.pos = it.Pos
|
||||
currShortcode.length = iter.Current().Pos - it.Pos
|
||||
currShortcode.pos = it.Pos()
|
||||
currShortcode.length = iter.Current().Pos() - it.Pos()
|
||||
if currShortcode.placeholder == "" {
|
||||
currShortcode.placeholder = createShortcodePlaceholder("s", currShortcode.ordinal)
|
||||
}
|
||||
|
||||
if currShortcode.name != "" {
|
||||
s.nameSet[currShortcode.name] = true
|
||||
s.addName(currShortcode.name)
|
||||
}
|
||||
|
||||
if currShortcode.params == nil {
|
||||
@@ -735,7 +754,7 @@ Loop:
|
||||
rn.AddShortcode(currShortcode)
|
||||
|
||||
case it.Type == pageparser.TypeEmoji:
|
||||
if emoji := helpers.Emoji(it.ValStr()); emoji != nil {
|
||||
if emoji := helpers.Emoji(it.ValStr(result.Input())); emoji != nil {
|
||||
rn.AddReplacement(emoji, it)
|
||||
} else {
|
||||
rn.AddBytes(it)
|
||||
@@ -743,7 +762,7 @@ Loop:
|
||||
case it.IsEOF():
|
||||
break Loop
|
||||
case it.IsError():
|
||||
err := fail(errors.New(it.ValStr()), it)
|
||||
err := fail(errors.New(it.ValStr(result.Input())), it)
|
||||
currShortcode.err = err
|
||||
return err
|
||||
|
||||
@@ -752,16 +771,14 @@ Loop:
|
||||
}
|
||||
}
|
||||
|
||||
if !frontMatterSet {
|
||||
if !frontMatterSet && withFrontMatter != nil {
|
||||
// Page content without front matter. Assign default front matter from
|
||||
// cascades etc.
|
||||
if err := meta.setMetadata(bucket, p, nil); err != nil {
|
||||
if err := withFrontMatter(nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
p.cmap = rn
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,9 @@ type pageCommon struct {
|
||||
// The parsed page content.
|
||||
pageContent
|
||||
|
||||
// Keeps track of the shortcodes on a page.
|
||||
shortcodeState *shortcodeHandler
|
||||
|
||||
// Set if feature enabled and this is in a Git repo.
|
||||
gitInfo *gitmap.GitInfo
|
||||
codeowners []string
|
||||
|
||||
@@ -33,21 +33,19 @@ type pageContent struct {
|
||||
|
||||
cmap *pageContentMap
|
||||
|
||||
shortcodeState *shortcodeHandler
|
||||
|
||||
source rawPageContent
|
||||
}
|
||||
|
||||
// returns the content to be processed by Blackfriday or similar.
|
||||
func (p pageContent) contentToRender(renderedShortcodes map[string]string) []byte {
|
||||
source := p.source.parsed.Input()
|
||||
// returns the content to be processed by Goldmark or similar.
|
||||
func (p pageContent) contentToRender(parsed pageparser.Result, pm *pageContentMap, renderedShortcodes map[string]string) []byte {
|
||||
source := parsed.Input()
|
||||
|
||||
c := make([]byte, 0, len(source)+(len(source)/10))
|
||||
|
||||
for _, it := range p.cmap.items {
|
||||
for _, it := range pm.items {
|
||||
switch v := it.(type) {
|
||||
case pageparser.Item:
|
||||
c = append(c, source[v.Pos:v.Pos+len(v.Val)]...)
|
||||
c = append(c, source[v.Pos():v.Pos()+len(v.Val(source))]...)
|
||||
case pageContentReplacement:
|
||||
c = append(c, v.val...)
|
||||
case *shortcode:
|
||||
|
||||
+7
-20
@@ -120,9 +120,8 @@ type pageMeta struct {
|
||||
|
||||
s *Site
|
||||
|
||||
renderingConfigOverrides map[string]any
|
||||
contentConverterInit sync.Once
|
||||
contentConverter converter.Converter
|
||||
contentConverterInit sync.Once
|
||||
contentConverter converter.Converter
|
||||
}
|
||||
|
||||
func (p *pageMeta) Aliases() []string {
|
||||
@@ -744,21 +743,10 @@ func (p *pageMeta) applyDefaultValues(n *contentNode) error {
|
||||
}
|
||||
}
|
||||
|
||||
if !p.f.IsZero() {
|
||||
var renderingConfigOverrides map[string]any
|
||||
bfParam := getParamToLower(p, "blackfriday")
|
||||
if bfParam != nil {
|
||||
renderingConfigOverrides = maps.ToStringMap(bfParam)
|
||||
}
|
||||
|
||||
p.renderingConfigOverrides = renderingConfigOverrides
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *pageMeta) newContentConverter(ps *pageState, markup string, renderingConfigOverrides map[string]any) (converter.Converter, error) {
|
||||
func (p *pageMeta) newContentConverter(ps *pageState, markup string) (converter.Converter, error) {
|
||||
if ps == nil {
|
||||
panic("no Page provided")
|
||||
}
|
||||
@@ -780,11 +768,10 @@ func (p *pageMeta) newContentConverter(ps *pageState, markup string, renderingCo
|
||||
|
||||
cpp, err := cp.New(
|
||||
converter.DocumentContext{
|
||||
Document: newPageForRenderHook(ps),
|
||||
DocumentID: id,
|
||||
DocumentName: path,
|
||||
Filename: filename,
|
||||
ConfigOverrides: renderingConfigOverrides,
|
||||
Document: newPageForRenderHook(ps),
|
||||
DocumentID: id,
|
||||
DocumentName: path,
|
||||
Filename: filename,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
@@ -66,6 +66,8 @@ func newPageBase(metaProvider *pageMeta) (*pageState, error) {
|
||||
},
|
||||
}
|
||||
|
||||
ps.shortcodeState = newShortcodeHandler(ps, ps.s)
|
||||
|
||||
siteAdapter := pageSiteAdapter{s: s, p: ps}
|
||||
|
||||
ps.pageMenus = &pageMenus{p: ps}
|
||||
|
||||
+75
-15
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/gohugoio/hugo/common/text"
|
||||
"github.com/gohugoio/hugo/common/types/hstring"
|
||||
"github.com/gohugoio/hugo/identity"
|
||||
"github.com/gohugoio/hugo/parser/pageparser"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
@@ -35,7 +36,7 @@ import (
|
||||
|
||||
"github.com/gohugoio/hugo/markup/converter"
|
||||
|
||||
"github.com/alecthomas/chroma/lexers"
|
||||
"github.com/alecthomas/chroma/v2/lexers"
|
||||
"github.com/gohugoio/hugo/lazy"
|
||||
|
||||
bp "github.com/gohugoio/hugo/bufferpool"
|
||||
@@ -117,7 +118,7 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err
|
||||
p.pageOutputTemplateVariationsState.Store(2)
|
||||
}
|
||||
|
||||
cp.workContent = p.contentToRender(cp.contentPlaceholders)
|
||||
cp.workContent = p.contentToRender(p.source.parsed, p.cmap, cp.contentPlaceholders)
|
||||
|
||||
isHTML := cp.p.m.markup == "html"
|
||||
|
||||
@@ -201,7 +202,7 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err
|
||||
})
|
||||
|
||||
cp.initPlain = cp.initMain.Branch(func() (any, error) {
|
||||
cp.plain = helpers.StripHTML(string(cp.content))
|
||||
cp.plain = tpl.StripHTML(string(cp.content))
|
||||
cp.plainWords = strings.Fields(cp.plain)
|
||||
cp.setWordCounts(p.m.isCJKLanguage)
|
||||
|
||||
@@ -336,7 +337,7 @@ func (p *pageContentOutput) RenderString(args ...any) (template.HTML, error) {
|
||||
return "", errors.New("want 1 or 2 arguments")
|
||||
}
|
||||
|
||||
var s string
|
||||
var contentToRender string
|
||||
opts := defaultRenderStringOpts
|
||||
sidx := 1
|
||||
|
||||
@@ -353,16 +354,16 @@ func (p *pageContentOutput) RenderString(args ...any) (template.HTML, error) {
|
||||
}
|
||||
}
|
||||
|
||||
contentToRender := args[sidx]
|
||||
contentToRenderv := args[sidx]
|
||||
|
||||
if _, ok := contentToRender.(hstring.RenderedString); ok {
|
||||
if _, ok := contentToRenderv.(hstring.RenderedString); ok {
|
||||
// This content is already rendered, this is potentially
|
||||
// a infinite recursion.
|
||||
return "", errors.New("text is already rendered, repeating it may cause infinite recursion")
|
||||
}
|
||||
|
||||
var err error
|
||||
s, err = cast.ToStringE(contentToRender)
|
||||
contentToRender, err = cast.ToStringE(contentToRenderv)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -375,26 +376,85 @@ func (p *pageContentOutput) RenderString(args ...any) (template.HTML, error) {
|
||||
if opts.Markup != "" && opts.Markup != p.p.m.markup {
|
||||
var err error
|
||||
// TODO(bep) consider cache
|
||||
conv, err = p.p.m.newContentConverter(p.p, opts.Markup, nil)
|
||||
conv, err = p.p.m.newContentConverter(p.p, opts.Markup)
|
||||
if err != nil {
|
||||
return "", p.p.wrapError(err)
|
||||
}
|
||||
}
|
||||
|
||||
c, err := p.renderContentWithConverter(conv, []byte(s), false)
|
||||
if err != nil {
|
||||
return "", p.p.wrapError(err)
|
||||
}
|
||||
var rendered []byte
|
||||
|
||||
b := c.Bytes()
|
||||
if strings.Contains(contentToRender, "{{") {
|
||||
// Probably a shortcode.
|
||||
parsed, err := pageparser.ParseMain(strings.NewReader(contentToRender), pageparser.Config{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pm := &pageContentMap{
|
||||
items: make([]any, 0, 20),
|
||||
}
|
||||
s := newShortcodeHandler(p.p, p.p.s)
|
||||
|
||||
if err := p.p.mapContentForResult(
|
||||
parsed,
|
||||
s,
|
||||
pm,
|
||||
opts.Markup,
|
||||
nil,
|
||||
); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
placeholders, hasShortcodeVariants, err := s.renderShortcodesForPage(p.p, p.f)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if hasShortcodeVariants {
|
||||
p.p.pageOutputTemplateVariationsState.Store(2)
|
||||
}
|
||||
|
||||
b, err := p.renderContentWithConverter(conv, p.p.contentToRender(parsed, pm, placeholders), false)
|
||||
if err != nil {
|
||||
return "", p.p.wrapError(err)
|
||||
}
|
||||
rendered = b.Bytes()
|
||||
|
||||
if p.placeholdersEnabled {
|
||||
// ToC was accessed via .Page.TableOfContents in the shortcode,
|
||||
// at a time when the ToC wasn't ready.
|
||||
if _, err := p.p.Content(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
placeholders[tocShortcodePlaceholder] = string(p.tableOfContents)
|
||||
}
|
||||
|
||||
if pm.hasNonMarkdownShortcode || p.placeholdersEnabled {
|
||||
rendered, err = replaceShortcodeTokens(rendered, placeholders)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// We need a consolidated view in $page.HasShortcode
|
||||
p.p.shortcodeState.transferNames(s)
|
||||
|
||||
} else {
|
||||
c, err := p.renderContentWithConverter(conv, []byte(contentToRender), false)
|
||||
if err != nil {
|
||||
return "", p.p.wrapError(err)
|
||||
}
|
||||
|
||||
rendered = c.Bytes()
|
||||
}
|
||||
|
||||
if opts.Display == "inline" {
|
||||
// We may have to rethink this in the future when we get other
|
||||
// renderers.
|
||||
b = p.p.s.ContentSpec.TrimShortHTML(b)
|
||||
rendered = p.p.s.ContentSpec.TrimShortHTML(rendered)
|
||||
}
|
||||
|
||||
return template.HTML(string(b)), nil
|
||||
return template.HTML(string(rendered)), nil
|
||||
}
|
||||
|
||||
func (p *pageContentOutput) RenderWithTemplateInfo(info tpl.Info, layout ...string) (template.HTML, error) {
|
||||
|
||||
@@ -36,6 +36,9 @@ func (pt pageTree) IsAncestor(other any) (bool, error) {
|
||||
}
|
||||
|
||||
ref1, ref2 := pt.p.getTreeRef(), tp.getTreeRef()
|
||||
if ref1 != nil && ref2 != nil && ref1.key == ref2.key {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if ref1 != nil && ref1.key == "/" {
|
||||
return true, nil
|
||||
@@ -50,10 +53,6 @@ func (pt pageTree) IsAncestor(other any) (bool, error) {
|
||||
return ref1.n.p.IsHome(), nil
|
||||
}
|
||||
|
||||
if ref1.key == ref2.key {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if strings.HasPrefix(ref2.key, ref1.key) {
|
||||
return true, nil
|
||||
}
|
||||
@@ -82,6 +81,9 @@ func (pt pageTree) IsDescendant(other any) (bool, error) {
|
||||
}
|
||||
|
||||
ref1, ref2 := pt.p.getTreeRef(), tp.getTreeRef()
|
||||
if ref1 != nil && ref2 != nil && ref1.key == ref2.key {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if ref2 != nil && ref2.key == "/" {
|
||||
return true, nil
|
||||
@@ -96,10 +98,6 @@ func (pt pageTree) IsDescendant(other any) (bool, error) {
|
||||
return ref2.n.p.IsHome(), nil
|
||||
}
|
||||
|
||||
if ref1.key == ref2.key {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if strings.HasPrefix(ref1.key, ref2.key) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
+6
-103
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/gohugoio/hugo/htesting"
|
||||
"github.com/gohugoio/hugo/markup/asciidocext"
|
||||
"github.com/gohugoio/hugo/markup/rst"
|
||||
"github.com/gohugoio/hugo/tpl"
|
||||
|
||||
"github.com/gohugoio/hugo/config"
|
||||
|
||||
@@ -40,7 +41,6 @@ import (
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
"github.com/gohugoio/hugo/helpers"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -238,16 +238,6 @@ the cylinder and strike me down. ## BB
|
||||
### BBB
|
||||
|
||||
"You're a great Granser," he cried delightedly, "always making believe them little marks mean something."
|
||||
`
|
||||
|
||||
simplePageWithAdditionalExtension = `+++
|
||||
[blackfriday]
|
||||
extensions = ["hardLineBreak"]
|
||||
+++
|
||||
first line.
|
||||
second line.
|
||||
|
||||
fourth line.
|
||||
`
|
||||
|
||||
simplePageWithURL = `---
|
||||
@@ -351,7 +341,7 @@ func normalizeExpected(ext, str string) string {
|
||||
default:
|
||||
return str
|
||||
case "html":
|
||||
return strings.Trim(helpers.StripHTML(str), " ")
|
||||
return strings.Trim(tpl.StripHTML(str), " ")
|
||||
case "ad":
|
||||
paragraphs := strings.Split(str, "</p>")
|
||||
expected := ""
|
||||
@@ -694,26 +684,6 @@ func TestPageWithShortCodeInSummary(t *testing.T) {
|
||||
testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithShortcodeInSummary)
|
||||
}
|
||||
|
||||
func TestPageWithAdditionalExtension(t *testing.T) {
|
||||
t.Parallel()
|
||||
cfg, fs := newTestCfg()
|
||||
cfg.Set("markup", map[string]any{
|
||||
"defaultMarkdownHandler": "blackfriday", // TODO(bep)
|
||||
})
|
||||
|
||||
c := qt.New(t)
|
||||
|
||||
writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageWithAdditionalExtension)
|
||||
|
||||
s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true})
|
||||
|
||||
c.Assert(len(s.RegularPages()), qt.Equals, 1)
|
||||
|
||||
p := s.RegularPages()[0]
|
||||
|
||||
checkPageContent(t, p, "<p>first line.<br />\nsecond line.</p>\n\n<p>fourth line.</p>\n")
|
||||
}
|
||||
|
||||
func TestTableOfContents(t *testing.T) {
|
||||
cfg, fs := newTestCfg()
|
||||
c := qt.New(t)
|
||||
@@ -1373,7 +1343,7 @@ title: "HTML Content"
|
||||
---
|
||||
`
|
||||
b.WithContent("regular.html", frontmatter+`<h1>Hugo</h1>`)
|
||||
b.WithContent("noblackfridayforyou.html", frontmatter+`**Hugo!**`)
|
||||
b.WithContent("nomarkdownforyou.html", frontmatter+`**Hugo!**`)
|
||||
b.WithContent("manualsummary.html", frontmatter+`
|
||||
<p>This is summary</p>
|
||||
<!--more-->
|
||||
@@ -1387,8 +1357,8 @@ title: "HTML Content"
|
||||
"Summary: Hugo|Truncated: false")
|
||||
|
||||
b.AssertFileContent(
|
||||
"public/noblackfridayforyou/index.html",
|
||||
"Permalink: http://example.com/noblackfridayforyou/|**Hugo!**|",
|
||||
"public/nomarkdownforyou/index.html",
|
||||
"Permalink: http://example.com/nomarkdownforyou/|**Hugo!**|",
|
||||
)
|
||||
|
||||
// https://github.com/gohugoio/hugo/issues/5723
|
||||
@@ -1736,6 +1706,7 @@ Len Summary: {{ len .Summary }}
|
||||
Len Content: {{ len .Content }}
|
||||
|
||||
SUMMARY:{{ .Summary }}:{{ len .Summary }}:END
|
||||
|
||||
`}
|
||||
|
||||
b := newTestSitesBuilder(t)
|
||||
@@ -2010,74 +1981,6 @@ Link with URL as text
|
||||
`)
|
||||
}
|
||||
|
||||
func TestBlackfridayDefault(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
b := newTestSitesBuilder(t).WithConfigFile("toml", `
|
||||
baseURL = "https://example.org"
|
||||
|
||||
[markup]
|
||||
defaultMarkdownHandler="blackfriday"
|
||||
[markup.highlight]
|
||||
noClasses=false
|
||||
[markup.goldmark]
|
||||
[markup.goldmark.renderer]
|
||||
unsafe=true
|
||||
|
||||
|
||||
`)
|
||||
// Use the new attribute syntax to make sure it's not Goldmark.
|
||||
b.WithTemplatesAdded("_default/single.html", `
|
||||
Title: {{ .Title }}
|
||||
Content: {{ .Content }}
|
||||
|
||||
`, "shortcodes/s.html", `## Code
|
||||
{{ .Inner }}
|
||||
`)
|
||||
|
||||
content := `
|
||||
+++
|
||||
title = "A Page!"
|
||||
+++
|
||||
|
||||
|
||||
## Code Fense in Shortcode
|
||||
|
||||
{{% s %}}
|
||||
S:
|
||||
{{% s %}}
|
||||
$$$bash {hl_lines=[1]}
|
||||
SHORT
|
||||
$$$
|
||||
{{% /s %}}
|
||||
{{% /s %}}
|
||||
|
||||
## Code Fence
|
||||
|
||||
$$$bash {hl_lines=[1]}
|
||||
MARKDOWN
|
||||
$$$
|
||||
|
||||
`
|
||||
content = strings.ReplaceAll(content, "$$$", "```")
|
||||
|
||||
for i, ext := range []string{"md", "html"} {
|
||||
b.WithContent(fmt.Sprintf("page%d.%s", i+1, ext), content)
|
||||
}
|
||||
|
||||
b.Build(BuildCfg{})
|
||||
|
||||
// Blackfriday does not support this extended attribute syntax.
|
||||
b.AssertFileContent("public/page1/index.html",
|
||||
`<pre tabindex="0"><code class="language-bash {hl_lines=[1]}" data-lang="bash {hl_lines=[1]}">SHORT</code></pre>`,
|
||||
`<pre tabindex="0"><code class="language-bash {hl_lines=[1]}" data-lang="bash {hl_lines=[1]}">MARKDOWN`,
|
||||
)
|
||||
|
||||
b.AssertFileContent("public/page2/index.html",
|
||||
`<pre tabindex="0"><code class="language-bash {hl_lines=[1]}" data-lang="bash {hl_lines=[1]}">SHORT`,
|
||||
)
|
||||
}
|
||||
|
||||
func TestPageCaseIssues(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
+12
-3
@@ -34,6 +34,8 @@ type Paths struct {
|
||||
Cfg config.Provider
|
||||
|
||||
BaseURL
|
||||
BaseURLString string
|
||||
BaseURLNoPathString string
|
||||
|
||||
// If the baseURL contains a base path, e.g. https://example.com/docs, then "/docs" will be the BasePath.
|
||||
BasePath string
|
||||
@@ -145,10 +147,17 @@ func New(fs *hugofs.Fs, cfg config.Provider) (*Paths, error) {
|
||||
}
|
||||
}
|
||||
|
||||
var baseURLString = baseURL.String()
|
||||
var baseURLNoPath = baseURL.URL()
|
||||
baseURLNoPath.Path = ""
|
||||
var baseURLNoPathString = baseURLNoPath.String()
|
||||
|
||||
p := &Paths{
|
||||
Fs: fs,
|
||||
Cfg: cfg,
|
||||
BaseURL: baseURL,
|
||||
Fs: fs,
|
||||
Cfg: cfg,
|
||||
BaseURL: baseURL,
|
||||
BaseURLString: baseURLString,
|
||||
BaseURLNoPathString: baseURLNoPathString,
|
||||
|
||||
DisablePathToLower: cfg.GetBool("disablePathToLower"),
|
||||
RemovePathAccents: cfg.GetBool("removePathAccents"),
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
// Copyright 2022 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 requiredF by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package hugolib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/common/loggers"
|
||||
)
|
||||
|
||||
func TestRenderString(t *testing.T) {
|
||||
b := newTestSitesBuilder(t)
|
||||
|
||||
b.WithTemplates("index.html", `
|
||||
{{ $p := site.GetPage "p1.md" }}
|
||||
{{ $optBlock := dict "display" "block" }}
|
||||
{{ $optOrg := dict "markup" "org" }}
|
||||
RSTART:{{ "**Bold Markdown**" | $p.RenderString }}:REND
|
||||
RSTART:{{ "**Bold Block Markdown**" | $p.RenderString $optBlock }}:REND
|
||||
RSTART:{{ "/italic org mode/" | $p.RenderString $optOrg }}:REND
|
||||
RSTART:{{ "## Header2" | $p.RenderString }}:REND
|
||||
|
||||
|
||||
`, "_default/_markup/render-heading.html", "Hook Heading: {{ .Level }}")
|
||||
|
||||
b.WithContent("p1.md", `---
|
||||
title: "p1"
|
||||
---
|
||||
`,
|
||||
)
|
||||
|
||||
b.Build(BuildCfg{})
|
||||
|
||||
b.AssertFileContent("public/index.html", `
|
||||
RSTART:<strong>Bold Markdown</strong>:REND
|
||||
RSTART:<p><strong>Bold Block Markdown</strong></p>
|
||||
RSTART:<em>italic org mode</em>:REND
|
||||
RSTART:Hook Heading: 2:REND
|
||||
`)
|
||||
}
|
||||
|
||||
// https://github.com/gohugoio/hugo/issues/6882
|
||||
func TestRenderStringOnListPage(t *testing.T) {
|
||||
renderStringTempl := `
|
||||
{{ .RenderString "**Hello**" }}
|
||||
`
|
||||
b := newTestSitesBuilder(t)
|
||||
b.WithContent("mysection/p1.md", `FOO`)
|
||||
b.WithTemplates(
|
||||
"index.html", renderStringTempl,
|
||||
"_default/list.html", renderStringTempl,
|
||||
"_default/single.html", renderStringTempl,
|
||||
)
|
||||
|
||||
b.Build(BuildCfg{})
|
||||
|
||||
for _, filename := range []string{
|
||||
"index.html",
|
||||
"mysection/index.html",
|
||||
"categories/index.html",
|
||||
"tags/index.html",
|
||||
"mysection/p1/index.html",
|
||||
} {
|
||||
b.AssertFileContent("public/"+filename, `<strong>Hello</strong>`)
|
||||
}
|
||||
}
|
||||
|
||||
// Issue 9433
|
||||
func TestRenderStringOnPageNotBackedByAFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
logger := loggers.NewWarningLogger()
|
||||
b := newTestSitesBuilder(t).WithLogger(logger).WithConfigFile("toml", `
|
||||
disableKinds = ["page", "section", "taxonomy", "term"]
|
||||
`)
|
||||
b.WithTemplates("index.html", `{{ .RenderString "**Hello**" }}`).WithContent("p1.md", "")
|
||||
b.BuildE(BuildCfg{})
|
||||
b.Assert(int(logger.LogCounters().WarnCounter.Count()), qt.Equals, 0)
|
||||
}
|
||||
|
||||
func TestRenderStringWithShortcode(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
filesTemplate := `
|
||||
-- config.toml --
|
||||
title = "Hugo Rocks!"
|
||||
enableInlineShortcodes = true
|
||||
-- content/p1/index.md --
|
||||
---
|
||||
title: "P1"
|
||||
---
|
||||
## First
|
||||
-- layouts/shortcodes/mark1.md --
|
||||
{{ .Inner }}
|
||||
-- layouts/shortcodes/mark2.md --
|
||||
1. Item Mark2 1
|
||||
1. Item Mark2 2
|
||||
1. Item Mark2 2-1
|
||||
1. Item Mark2 3
|
||||
-- layouts/shortcodes/myhthml.html --
|
||||
Title: {{ .Page.Title }}
|
||||
TableOfContents: {{ .Page.TableOfContents }}
|
||||
Page Type: {{ printf "%T" .Page }}
|
||||
-- layouts/_default/single.html --
|
||||
{{ .RenderString "Markdown: {{% mark2 %}}|HTML: {{< myhthml >}}|Inline: {{< foo.inline >}}{{ site.Title }}{{< /foo.inline >}}|" }}
|
||||
HasShortcode: mark2:{{ .HasShortcode "mark2" }}:true
|
||||
HasShortcode: foo:{{ .HasShortcode "foo" }}:false
|
||||
|
||||
`
|
||||
|
||||
t.Run("Basic", func(t *testing.T) {
|
||||
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: filesTemplate,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/p1/index.html",
|
||||
"<p>Markdown: 1. Item Mark2 1</p>\n<ol>\n<li>Item Mark2 2\n<ol>\n<li>Item Mark2 2-1</li>\n</ol>\n</li>\n<li>Item Mark2 3|",
|
||||
"<a href=\"#first\">First</a>", // ToC
|
||||
`
|
||||
HTML: Title: P1
|
||||
Inline: Hugo Rocks!
|
||||
HasShortcode: mark2:true:true
|
||||
HasShortcode: foo:false:false
|
||||
Page Type: *hugolib.pageForShortcode`,
|
||||
)
|
||||
|
||||
})
|
||||
|
||||
t.Run("Edit shortcode", func(t *testing.T) {
|
||||
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: filesTemplate,
|
||||
Running: true,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.EditFiles("layouts/shortcodes/myhthml.html", "Edit shortcode").Build()
|
||||
|
||||
b.AssertFileContent("public/p1/index.html",
|
||||
`Edit shortcode`,
|
||||
)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// Issue 9959
|
||||
func TestRenderStringWithShortcodeInPageWithNoContentFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- config.toml --
|
||||
-- layouts/shortcodes/myshort.html --
|
||||
Page Kind: {{ .Page.Kind }}
|
||||
-- layouts/index.html --
|
||||
Short: {{ .RenderString "{{< myshort >}}" }}
|
||||
Has myshort: {{ .HasShortcode "myshort" }}
|
||||
Has other: {{ .HasShortcode "other" }}
|
||||
|
||||
`
|
||||
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/index.html",
|
||||
`
|
||||
Page Kind: home
|
||||
Has myshort: true
|
||||
Has other: false
|
||||
`)
|
||||
|
||||
}
|
||||
+95
-18
@@ -62,6 +62,12 @@ type ShortcodeWithPage struct {
|
||||
// this ordinal will represent the position of this shortcode in the page content.
|
||||
Ordinal int
|
||||
|
||||
// Indentation before the opening shortcode in the source.
|
||||
indentation string
|
||||
|
||||
innerDeindentInit sync.Once
|
||||
innerDeindent template.HTML
|
||||
|
||||
// pos is the position in bytes in the source file. Used for error logging.
|
||||
posInit sync.Once
|
||||
posOffset int
|
||||
@@ -70,6 +76,27 @@ type ShortcodeWithPage struct {
|
||||
scratch *maps.Scratch
|
||||
}
|
||||
|
||||
// InnerDeindent returns the (potentially de-indented) inner content of the shortcode.
|
||||
func (scp *ShortcodeWithPage) InnerDeindent() template.HTML {
|
||||
if scp.indentation == "" {
|
||||
return scp.Inner
|
||||
}
|
||||
scp.innerDeindentInit.Do(func() {
|
||||
b := bp.GetBuffer()
|
||||
text.VisitLinesAfter(string(scp.Inner), func(s string) {
|
||||
if strings.HasPrefix(s, scp.indentation) {
|
||||
b.WriteString(strings.TrimPrefix(s, scp.indentation))
|
||||
} else {
|
||||
b.WriteString(s)
|
||||
}
|
||||
})
|
||||
scp.innerDeindent = template.HTML(b.String())
|
||||
bp.PutBuffer(b)
|
||||
})
|
||||
|
||||
return scp.innerDeindent
|
||||
}
|
||||
|
||||
// Position returns this shortcode's detailed position. Note that this information
|
||||
// may be expensive to calculate, so only use this in error situations.
|
||||
func (scp *ShortcodeWithPage) Position() text.Position {
|
||||
@@ -170,11 +197,13 @@ type shortcode struct {
|
||||
ordinal int
|
||||
err error
|
||||
|
||||
indentation string // indentation from source.
|
||||
|
||||
info tpl.Info // One of the output formats (arbitrary)
|
||||
templs []tpl.Template // All output formats
|
||||
|
||||
// If set, the rendered shortcode is sent as part of the surrounding content
|
||||
// to Blackfriday and similar.
|
||||
// to Goldmark and similar.
|
||||
// Before Hug0 0.55 we didn't send any shortcode output to the markup
|
||||
// renderer, and this flag told Hugo to process the {{ .Inner }} content
|
||||
// separately.
|
||||
@@ -182,7 +211,7 @@ type shortcode struct {
|
||||
// {{ $_hugo_config := `{ "version": 1 }`}}
|
||||
doMarkup bool
|
||||
|
||||
// the placeholder in the source when passed to Blackfriday etc.
|
||||
// the placeholder in the source when passed to Goldmark etc.
|
||||
// This also identifies the rendered shortcode.
|
||||
placeholder string
|
||||
|
||||
@@ -248,13 +277,14 @@ type shortcodeHandler struct {
|
||||
shortcodes []*shortcode
|
||||
|
||||
// All the shortcode names in this set.
|
||||
nameSet map[string]bool
|
||||
nameSet map[string]bool
|
||||
nameSetMu sync.RWMutex
|
||||
|
||||
// Configuration
|
||||
enableInlineShortcodes bool
|
||||
}
|
||||
|
||||
func newShortcodeHandler(p *pageState, s *Site, placeholderFunc func() string) *shortcodeHandler {
|
||||
func newShortcodeHandler(p *pageState, s *Site) *shortcodeHandler {
|
||||
sh := &shortcodeHandler{
|
||||
p: p,
|
||||
s: s,
|
||||
@@ -323,7 +353,7 @@ func renderShortcode(
|
||||
hasVariants = hasVariants || more
|
||||
}
|
||||
|
||||
data := &ShortcodeWithPage{Ordinal: sc.ordinal, posOffset: sc.pos, Params: sc.params, Page: newPageForShortcode(p), Parent: parent, Name: sc.name}
|
||||
data := &ShortcodeWithPage{Ordinal: sc.ordinal, posOffset: sc.pos, indentation: sc.indentation, Params: sc.params, Page: newPageForShortcode(p), Parent: parent, Name: sc.name}
|
||||
if sc.params != nil {
|
||||
data.IsNamedParams = reflect.TypeOf(sc.params).Kind() == reflect.Map
|
||||
}
|
||||
@@ -398,6 +428,22 @@ func renderShortcode(
|
||||
return "", false, fe
|
||||
}
|
||||
|
||||
if len(sc.inner) == 0 && len(sc.indentation) > 0 {
|
||||
b := bp.GetBuffer()
|
||||
i := 0
|
||||
text.VisitLinesAfter(result, func(line string) {
|
||||
// The first line is correctly indented.
|
||||
if i > 0 {
|
||||
b.WriteString(sc.indentation)
|
||||
}
|
||||
i++
|
||||
b.WriteString(line)
|
||||
})
|
||||
|
||||
result = b.String()
|
||||
bp.PutBuffer(b)
|
||||
}
|
||||
|
||||
return result, hasVariants, err
|
||||
}
|
||||
|
||||
@@ -405,6 +451,28 @@ func (s *shortcodeHandler) hasShortcodes() bool {
|
||||
return s != nil && len(s.shortcodes) > 0
|
||||
}
|
||||
|
||||
func (s *shortcodeHandler) addName(name string) {
|
||||
s.nameSetMu.Lock()
|
||||
defer s.nameSetMu.Unlock()
|
||||
s.nameSet[name] = true
|
||||
}
|
||||
|
||||
func (s *shortcodeHandler) transferNames(in *shortcodeHandler) {
|
||||
s.nameSetMu.Lock()
|
||||
defer s.nameSetMu.Unlock()
|
||||
for k := range in.nameSet {
|
||||
s.nameSet[k] = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *shortcodeHandler) hasName(name string) bool {
|
||||
s.nameSetMu.RLock()
|
||||
defer s.nameSetMu.RUnlock()
|
||||
_, ok := s.nameSet[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s *shortcodeHandler) renderShortcodesForPage(p *pageState, f output.Format) (map[string]string, bool, error) {
|
||||
rendered := make(map[string]string)
|
||||
|
||||
@@ -441,19 +509,28 @@ func (s *shortcodeHandler) parseError(err error, input []byte, pos int) error {
|
||||
// pageTokens state:
|
||||
// - before: positioned just before the shortcode start
|
||||
// - after: shortcode(s) consumed (plural when they are nested)
|
||||
func (s *shortcodeHandler) extractShortcode(ordinal, level int, pt *pageparser.Iterator) (*shortcode, error) {
|
||||
func (s *shortcodeHandler) extractShortcode(ordinal, level int, source []byte, pt *pageparser.Iterator) (*shortcode, error) {
|
||||
if s == nil {
|
||||
panic("handler nil")
|
||||
}
|
||||
sc := &shortcode{ordinal: ordinal}
|
||||
|
||||
// Back up one to identify any indentation.
|
||||
if pt.Pos() > 0 {
|
||||
pt.Backup()
|
||||
item := pt.Next()
|
||||
if item.IsIndentation() {
|
||||
sc.indentation = item.ValStr(source)
|
||||
}
|
||||
}
|
||||
|
||||
cnt := 0
|
||||
nestedOrdinal := 0
|
||||
nextLevel := level + 1
|
||||
const errorPrefix = "failed to extract shortcode"
|
||||
|
||||
fail := func(err error, i pageparser.Item) error {
|
||||
return s.parseError(fmt.Errorf("%s: %w", errorPrefix, err), pt.Input(), i.Pos)
|
||||
return s.parseError(fmt.Errorf("%s: %w", errorPrefix, err), source, i.Pos())
|
||||
}
|
||||
|
||||
Loop:
|
||||
@@ -473,10 +550,10 @@ Loop:
|
||||
if cnt > 0 {
|
||||
// nested shortcode; append it to inner content
|
||||
pt.Backup()
|
||||
nested, err := s.extractShortcode(nestedOrdinal, nextLevel, pt)
|
||||
nested, err := s.extractShortcode(nestedOrdinal, nextLevel, source, pt)
|
||||
nestedOrdinal++
|
||||
if nested != nil && nested.name != "" {
|
||||
s.nameSet[nested.name] = true
|
||||
s.addName(nested.name)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
@@ -512,7 +589,7 @@ Loop:
|
||||
// return that error, more specific
|
||||
continue
|
||||
}
|
||||
return sc, fail(fmt.Errorf("shortcode %q has no .Inner, yet a closing tag was provided", next.Val), next)
|
||||
return sc, fail(fmt.Errorf("shortcode %q has no .Inner, yet a closing tag was provided", next.ValStr(source)), next)
|
||||
}
|
||||
}
|
||||
if next.IsRightShortcodeDelim() {
|
||||
@@ -525,11 +602,11 @@ Loop:
|
||||
|
||||
return sc, nil
|
||||
case currItem.IsText():
|
||||
sc.inner = append(sc.inner, currItem.ValStr())
|
||||
sc.inner = append(sc.inner, currItem.ValStr(source))
|
||||
case currItem.Type == pageparser.TypeEmoji:
|
||||
// TODO(bep) avoid the duplication of these "text cases", to prevent
|
||||
// more of #6504 in the future.
|
||||
val := currItem.ValStr()
|
||||
val := currItem.ValStr(source)
|
||||
if emoji := helpers.Emoji(val); emoji != nil {
|
||||
sc.inner = append(sc.inner, string(emoji))
|
||||
} else {
|
||||
@@ -537,7 +614,7 @@ Loop:
|
||||
}
|
||||
case currItem.IsShortcodeName():
|
||||
|
||||
sc.name = currItem.ValStr()
|
||||
sc.name = currItem.ValStr(source)
|
||||
|
||||
// Used to check if the template expects inner content.
|
||||
templs := s.s.Tmpl().LookupVariants(sc.name)
|
||||
@@ -548,7 +625,7 @@ Loop:
|
||||
sc.info = templs[0].(tpl.Info)
|
||||
sc.templs = templs
|
||||
case currItem.IsInlineShortcodeName():
|
||||
sc.name = currItem.ValStr()
|
||||
sc.name = currItem.ValStr(source)
|
||||
sc.isInline = true
|
||||
case currItem.IsShortcodeParam():
|
||||
if !pt.IsValueNext() {
|
||||
@@ -557,11 +634,11 @@ Loop:
|
||||
// named params
|
||||
if sc.params == nil {
|
||||
params := make(map[string]any)
|
||||
params[currItem.ValStr()] = pt.Next().ValTyped()
|
||||
params[currItem.ValStr(source)] = pt.Next().ValTyped(source)
|
||||
sc.params = params
|
||||
} else {
|
||||
if params, ok := sc.params.(map[string]any); ok {
|
||||
params[currItem.ValStr()] = pt.Next().ValTyped()
|
||||
params[currItem.ValStr(source)] = pt.Next().ValTyped(source)
|
||||
} else {
|
||||
return sc, errShortCodeIllegalState
|
||||
}
|
||||
@@ -570,11 +647,11 @@ Loop:
|
||||
// positional params
|
||||
if sc.params == nil {
|
||||
var params []any
|
||||
params = append(params, currItem.ValTyped())
|
||||
params = append(params, currItem.ValTyped(source))
|
||||
sc.params = params
|
||||
} else {
|
||||
if params, ok := sc.params.([]any); ok {
|
||||
params = append(params, currItem.ValTyped())
|
||||
params = append(params, currItem.ValTyped(source))
|
||||
sc.params = params
|
||||
} else {
|
||||
return sc, errShortCodeIllegalState
|
||||
|
||||
+218
-561
@@ -21,332 +21,13 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/gohugoio/hugo/config"
|
||||
"github.com/gohugoio/hugo/markup/asciidocext"
|
||||
"github.com/gohugoio/hugo/markup/rst"
|
||||
|
||||
"github.com/gohugoio/hugo/parser/pageparser"
|
||||
"github.com/gohugoio/hugo/resources/page"
|
||||
|
||||
"github.com/gohugoio/hugo/deps"
|
||||
"github.com/gohugoio/hugo/tpl"
|
||||
"github.com/spf13/cast"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
)
|
||||
|
||||
func CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func(templ tpl.TemplateManager) error) {
|
||||
t.Helper()
|
||||
CheckShortCodeMatchAndError(t, input, expected, withTemplate, false)
|
||||
}
|
||||
|
||||
func CheckShortCodeMatchAndError(t *testing.T, input, expected string, withTemplate func(templ tpl.TemplateManager) error, expectError bool) {
|
||||
t.Helper()
|
||||
cfg, fs := newTestCfg()
|
||||
|
||||
cfg.Set("markup", map[string]any{
|
||||
"defaultMarkdownHandler": "blackfriday", // TODO(bep)
|
||||
})
|
||||
|
||||
c := qt.New(t)
|
||||
|
||||
// Need some front matter, see https://github.com/gohugoio/hugo/issues/2337
|
||||
contentFile := `---
|
||||
title: "Title"
|
||||
---
|
||||
` + input
|
||||
|
||||
writeSource(t, fs, "content/simple.md", contentFile)
|
||||
|
||||
b := newTestSitesBuilderFromDepsCfg(t, deps.DepsCfg{Fs: fs, Cfg: cfg, WithTemplate: withTemplate}).WithNothingAdded()
|
||||
err := b.BuildE(BuildCfg{})
|
||||
|
||||
if err != nil && !expectError {
|
||||
t.Fatalf("Shortcode rendered error %s.", err)
|
||||
}
|
||||
|
||||
if expectError {
|
||||
c.Assert(err, qt.ErrorMatches, expected)
|
||||
return
|
||||
}
|
||||
|
||||
h := b.H
|
||||
c.Assert(len(h.Sites), qt.Equals, 1)
|
||||
|
||||
c.Assert(len(h.Sites[0].RegularPages()), qt.Equals, 1)
|
||||
|
||||
output := strings.TrimSpace(content(h.Sites[0].RegularPages()[0]))
|
||||
output = strings.TrimPrefix(output, "<p>")
|
||||
output = strings.TrimSuffix(output, "</p>")
|
||||
|
||||
expected = strings.TrimSpace(expected)
|
||||
|
||||
if output != expected {
|
||||
t.Fatalf("Shortcode render didn't match. got \n%q but expected \n%q", output, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonSC(t *testing.T) {
|
||||
t.Parallel()
|
||||
// notice the syntax diff from 0.12, now comment delims must be added
|
||||
CheckShortCodeMatch(t, "{{%/* movie 47238zzb */%}}", "{{% movie 47238zzb %}}", nil)
|
||||
}
|
||||
|
||||
// Issue #929
|
||||
func TestHyphenatedSC(t *testing.T) {
|
||||
t.Parallel()
|
||||
wt := func(tem tpl.TemplateManager) error {
|
||||
tem.AddTemplate("_internal/shortcodes/hyphenated-video.html", `Playing Video {{ .Get 0 }}`)
|
||||
return nil
|
||||
}
|
||||
|
||||
CheckShortCodeMatch(t, "{{< hyphenated-video 47238zzb >}}", "Playing Video 47238zzb", wt)
|
||||
}
|
||||
|
||||
// Issue #1753
|
||||
func TestNoTrailingNewline(t *testing.T) {
|
||||
t.Parallel()
|
||||
wt := func(tem tpl.TemplateManager) error {
|
||||
tem.AddTemplate("_internal/shortcodes/a.html", `{{ .Get 0 }}`)
|
||||
return nil
|
||||
}
|
||||
|
||||
CheckShortCodeMatch(t, "ab{{< a c >}}d", "abcd", wt)
|
||||
}
|
||||
|
||||
func TestPositionalParamSC(t *testing.T) {
|
||||
t.Parallel()
|
||||
wt := func(tem tpl.TemplateManager) error {
|
||||
tem.AddTemplate("_internal/shortcodes/video.html", `Playing Video {{ .Get 0 }}`)
|
||||
return nil
|
||||
}
|
||||
|
||||
CheckShortCodeMatch(t, "{{< video 47238zzb >}}", "Playing Video 47238zzb", wt)
|
||||
CheckShortCodeMatch(t, "{{< video 47238zzb 132 >}}", "Playing Video 47238zzb", wt)
|
||||
CheckShortCodeMatch(t, "{{<video 47238zzb>}}", "Playing Video 47238zzb", wt)
|
||||
CheckShortCodeMatch(t, "{{<video 47238zzb >}}", "Playing Video 47238zzb", wt)
|
||||
CheckShortCodeMatch(t, "{{< video 47238zzb >}}", "Playing Video 47238zzb", wt)
|
||||
}
|
||||
|
||||
func TestPositionalParamIndexOutOfBounds(t *testing.T) {
|
||||
t.Parallel()
|
||||
wt := func(tem tpl.TemplateManager) error {
|
||||
tem.AddTemplate("_internal/shortcodes/video.html", `Playing Video {{ with .Get 1 }}{{ . }}{{ else }}Missing{{ end }}`)
|
||||
return nil
|
||||
}
|
||||
CheckShortCodeMatch(t, "{{< video 47238zzb >}}", "Playing Video Missing", wt)
|
||||
}
|
||||
|
||||
// #5071
|
||||
func TestShortcodeRelated(t *testing.T) {
|
||||
t.Parallel()
|
||||
wt := func(tem tpl.TemplateManager) error {
|
||||
tem.AddTemplate("_internal/shortcodes/a.html", `{{ len (.Site.RegularPages.Related .Page) }}`)
|
||||
return nil
|
||||
}
|
||||
|
||||
CheckShortCodeMatch(t, "{{< a >}}", "0", wt)
|
||||
}
|
||||
|
||||
func TestShortcodeInnerMarkup(t *testing.T) {
|
||||
t.Parallel()
|
||||
wt := func(tem tpl.TemplateManager) error {
|
||||
tem.AddTemplate("shortcodes/a.html", `<div>{{ .Inner }}</div>`)
|
||||
tem.AddTemplate("shortcodes/b.html", `**Bold**: <div>{{ .Inner }}</div>`)
|
||||
return nil
|
||||
}
|
||||
|
||||
CheckShortCodeMatch(t,
|
||||
"{{< a >}}B: <div>{{% b %}}**Bold**{{% /b %}}</div>{{< /a >}}",
|
||||
// This assertion looks odd, but is correct: for inner shortcodes with
|
||||
// the {{% we treats the .Inner content as markup, but not the shortcode
|
||||
// itself.
|
||||
"<div>B: <div>**Bold**: <div><strong>Bold</strong></div></div></div>",
|
||||
wt)
|
||||
|
||||
CheckShortCodeMatch(t,
|
||||
"{{% b %}}This is **B**: {{< b >}}This is B{{< /b>}}{{% /b %}}",
|
||||
"<strong>Bold</strong>: <div>This is <strong>B</strong>: <strong>Bold</strong>: <div>This is B</div></div>",
|
||||
wt)
|
||||
}
|
||||
|
||||
// some repro issues for panics in Go Fuzz testing
|
||||
|
||||
func TestNamedParamSC(t *testing.T) {
|
||||
t.Parallel()
|
||||
wt := func(tem tpl.TemplateManager) error {
|
||||
tem.AddTemplate("_internal/shortcodes/img.html", `<img{{ with .Get "src" }} src="{{.}}"{{end}}{{with .Get "class"}} class="{{.}}"{{end}}>`)
|
||||
return nil
|
||||
}
|
||||
CheckShortCodeMatch(t, `{{< img src="one" >}}`, `<img src="one">`, wt)
|
||||
CheckShortCodeMatch(t, `{{< img class="aspen" >}}`, `<img class="aspen">`, wt)
|
||||
CheckShortCodeMatch(t, `{{< img src= "one" >}}`, `<img src="one">`, wt)
|
||||
CheckShortCodeMatch(t, `{{< img src ="one" >}}`, `<img src="one">`, wt)
|
||||
CheckShortCodeMatch(t, `{{< img src = "one" >}}`, `<img src="one">`, wt)
|
||||
CheckShortCodeMatch(t, `{{< img src = "one" class = "aspen grove" >}}`, `<img src="one" class="aspen grove">`, wt)
|
||||
}
|
||||
|
||||
// Issue #2294
|
||||
func TestNestedNamedMissingParam(t *testing.T) {
|
||||
t.Parallel()
|
||||
wt := func(tem tpl.TemplateManager) error {
|
||||
tem.AddTemplate("_internal/shortcodes/acc.html", `<div class="acc">{{ .Inner }}</div>`)
|
||||
tem.AddTemplate("_internal/shortcodes/div.html", `<div {{with .Get "class"}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
|
||||
tem.AddTemplate("_internal/shortcodes/div2.html", `<div {{with .Get 0}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
|
||||
return nil
|
||||
}
|
||||
CheckShortCodeMatch(t,
|
||||
`{{% acc %}}{{% div %}}d1{{% /div %}}{{% div2 %}}d2{{% /div2 %}}{{% /acc %}}`,
|
||||
"<div class=\"acc\"><div >d1</div><div >d2</div></div>", wt)
|
||||
}
|
||||
|
||||
func TestIsNamedParamsSC(t *testing.T) {
|
||||
t.Parallel()
|
||||
wt := func(tem tpl.TemplateManager) error {
|
||||
tem.AddTemplate("_internal/shortcodes/bynameorposition.html", `{{ with .Get "id" }}Named: {{ . }}{{ else }}Pos: {{ .Get 0 }}{{ end }}`)
|
||||
tem.AddTemplate("_internal/shortcodes/ifnamedparams.html", `<div id="{{ if .IsNamedParams }}{{ .Get "id" }}{{ else }}{{ .Get 0 }}{{end}}">`)
|
||||
return nil
|
||||
}
|
||||
CheckShortCodeMatch(t, `{{< ifnamedparams id="name" >}}`, `<div id="name">`, wt)
|
||||
CheckShortCodeMatch(t, `{{< ifnamedparams position >}}`, `<div id="position">`, wt)
|
||||
CheckShortCodeMatch(t, `{{< bynameorposition id="name" >}}`, `Named: name`, wt)
|
||||
CheckShortCodeMatch(t, `{{< bynameorposition position >}}`, `Pos: position`, wt)
|
||||
}
|
||||
|
||||
func TestInnerSC(t *testing.T) {
|
||||
t.Parallel()
|
||||
wt := func(tem tpl.TemplateManager) error {
|
||||
tem.AddTemplate("_internal/shortcodes/inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
|
||||
return nil
|
||||
}
|
||||
CheckShortCodeMatch(t, `{{< inside class="aspen" >}}`, `<div class="aspen"></div>`, wt)
|
||||
CheckShortCodeMatch(t, `{{< inside class="aspen" >}}More Here{{< /inside >}}`, "<div class=\"aspen\">More Here</div>", wt)
|
||||
CheckShortCodeMatch(t, `{{< inside >}}More Here{{< /inside >}}`, "<div>More Here</div>", wt)
|
||||
}
|
||||
|
||||
func TestInnerSCWithMarkdown(t *testing.T) {
|
||||
t.Parallel()
|
||||
wt := func(tem tpl.TemplateManager) error {
|
||||
// Note: In Hugo 0.55 we made it so any outer {{%'s inner content was rendered as part of the surrounding
|
||||
// markup. This solved lots of problems, but it also meant that this test had to be adjusted.
|
||||
tem.AddTemplate("_internal/shortcodes/wrapper.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
|
||||
tem.AddTemplate("_internal/shortcodes/inside.html", `{{ .Inner }}`)
|
||||
return nil
|
||||
}
|
||||
CheckShortCodeMatch(t, `{{< wrapper >}}{{% inside %}}
|
||||
# More Here
|
||||
|
||||
[link](http://spf13.com) and text
|
||||
|
||||
{{% /inside %}}{{< /wrapper >}}`, "<div><h1 id=\"more-here\">More Here</h1>\n\n<p><a href=\"http://spf13.com\">link</a> and text</p>\n</div>", wt)
|
||||
}
|
||||
|
||||
func TestEmbeddedSC(t *testing.T) {
|
||||
t.Parallel()
|
||||
CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" %}}`, "<figure class=\"bananas orange\"><img src=\"/found/here\"/>\n</figure>", nil)
|
||||
CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" caption="This is a caption" %}}`, "<figure class=\"bananas orange\"><img src=\"/found/here\"\n alt=\"This is a caption\"/><figcaption>\n <p>This is a caption</p>\n </figcaption>\n</figure>", nil)
|
||||
}
|
||||
|
||||
func TestNestedSC(t *testing.T) {
|
||||
t.Parallel()
|
||||
wt := func(tem tpl.TemplateManager) error {
|
||||
tem.AddTemplate("_internal/shortcodes/scn1.html", `<div>Outer, inner is {{ .Inner }}</div>`)
|
||||
tem.AddTemplate("_internal/shortcodes/scn2.html", `<div>SC2</div>`)
|
||||
return nil
|
||||
}
|
||||
CheckShortCodeMatch(t, `{{% scn1 %}}{{% scn2 %}}{{% /scn1 %}}`, "<div>Outer, inner is <div>SC2</div></div>", wt)
|
||||
|
||||
CheckShortCodeMatch(t, `{{< scn1 >}}{{% scn2 %}}{{< /scn1 >}}`, "<div>Outer, inner is <div>SC2</div></div>", wt)
|
||||
}
|
||||
|
||||
func TestNestedComplexSC(t *testing.T) {
|
||||
t.Parallel()
|
||||
wt := func(tem tpl.TemplateManager) error {
|
||||
tem.AddTemplate("_internal/shortcodes/row.html", `-row-{{ .Inner}}-rowStop-`)
|
||||
tem.AddTemplate("_internal/shortcodes/column.html", `-col-{{.Inner }}-colStop-`)
|
||||
tem.AddTemplate("_internal/shortcodes/aside.html", `-aside-{{ .Inner }}-asideStop-`)
|
||||
return nil
|
||||
}
|
||||
CheckShortCodeMatch(t, `{{< row >}}1-s{{% column %}}2-**s**{{< aside >}}3-**s**{{< /aside >}}4-s{{% /column %}}5-s{{< /row >}}6-s`,
|
||||
"-row-1-s-col-2-<strong>s</strong>-aside-3-<strong>s</strong>-asideStop-4-s-colStop-5-s-rowStop-6-s", wt)
|
||||
|
||||
// turn around the markup flag
|
||||
CheckShortCodeMatch(t, `{{% row %}}1-s{{< column >}}2-**s**{{% aside %}}3-**s**{{% /aside %}}4-s{{< /column >}}5-s{{% /row %}}6-s`,
|
||||
"-row-1-s-col-2-<strong>s</strong>-aside-3-<strong>s</strong>-asideStop-4-s-colStop-5-s-rowStop-6-s", wt)
|
||||
}
|
||||
|
||||
func TestParentShortcode(t *testing.T) {
|
||||
t.Parallel()
|
||||
wt := func(tem tpl.TemplateManager) error {
|
||||
tem.AddTemplate("_internal/shortcodes/r1.html", `1: {{ .Get "pr1" }} {{ .Inner }}`)
|
||||
tem.AddTemplate("_internal/shortcodes/r2.html", `2: {{ .Parent.Get "pr1" }}{{ .Get "pr2" }} {{ .Inner }}`)
|
||||
tem.AddTemplate("_internal/shortcodes/r3.html", `3: {{ .Parent.Parent.Get "pr1" }}{{ .Parent.Get "pr2" }}{{ .Get "pr3" }} {{ .Inner }}`)
|
||||
return nil
|
||||
}
|
||||
CheckShortCodeMatch(t, `{{< r1 pr1="p1" >}}1: {{< r2 pr2="p2" >}}2: {{< r3 pr3="p3" >}}{{< /r3 >}}{{< /r2 >}}{{< /r1 >}}`,
|
||||
"1: p1 1: 2: p1p2 2: 3: p1p2p3 ", wt)
|
||||
}
|
||||
|
||||
func TestFigureOnlySrc(t *testing.T) {
|
||||
t.Parallel()
|
||||
CheckShortCodeMatch(t, `{{< figure src="/found/here" >}}`, "<figure><img src=\"/found/here\"/>\n</figure>", nil)
|
||||
}
|
||||
|
||||
func TestFigureCaptionAttrWithMarkdown(t *testing.T) {
|
||||
t.Parallel()
|
||||
CheckShortCodeMatch(t, `{{< figure src="/found/here" caption="Something **bold** _italic_" >}}`, "<figure><img src=\"/found/here\"\n alt=\"Something bold italic\"/><figcaption>\n <p>Something <strong>bold</strong> <em>italic</em></p>\n </figcaption>\n</figure>", nil)
|
||||
CheckShortCodeMatch(t, `{{< figure src="/found/here" attr="Something **bold** _italic_" >}}`, "<figure><img src=\"/found/here\"/><figcaption>\n <p>Something <strong>bold</strong> <em>italic</em></p>\n </figcaption>\n</figure>", nil)
|
||||
}
|
||||
|
||||
func TestFigureImgWidth(t *testing.T) {
|
||||
t.Parallel()
|
||||
CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" alt="apple" width="100px" %}}`, "<figure class=\"bananas orange\"><img src=\"/found/here\"\n alt=\"apple\" width=\"100px\"/>\n</figure>", nil)
|
||||
}
|
||||
|
||||
func TestFigureImgHeight(t *testing.T) {
|
||||
t.Parallel()
|
||||
CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" alt="apple" height="100px" %}}`, "<figure class=\"bananas orange\"><img src=\"/found/here\"\n alt=\"apple\" height=\"100px\"/>\n</figure>", nil)
|
||||
}
|
||||
|
||||
func TestFigureImgWidthAndHeight(t *testing.T) {
|
||||
t.Parallel()
|
||||
CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" alt="apple" width="50" height="100" %}}`, "<figure class=\"bananas orange\"><img src=\"/found/here\"\n alt=\"apple\" width=\"50\" height=\"100\"/>\n</figure>", nil)
|
||||
}
|
||||
|
||||
func TestFigureLinkNoTarget(t *testing.T) {
|
||||
t.Parallel()
|
||||
CheckShortCodeMatch(t, `{{< figure src="/found/here" link="/jump/here/on/clicking" >}}`, "<figure><a href=\"/jump/here/on/clicking\"><img src=\"/found/here\"/></a>\n</figure>", nil)
|
||||
}
|
||||
|
||||
func TestFigureLinkWithTarget(t *testing.T) {
|
||||
t.Parallel()
|
||||
CheckShortCodeMatch(t, `{{< figure src="/found/here" link="/jump/here/on/clicking" target="_self" >}}`, "<figure><a href=\"/jump/here/on/clicking\" target=\"_self\"><img src=\"/found/here\"/></a>\n</figure>", nil)
|
||||
}
|
||||
|
||||
func TestFigureLinkWithTargetAndRel(t *testing.T) {
|
||||
t.Parallel()
|
||||
CheckShortCodeMatch(t, `{{< figure src="/found/here" link="/jump/here/on/clicking" target="_blank" rel="noopener" >}}`, "<figure><a href=\"/jump/here/on/clicking\" target=\"_blank\" rel=\"noopener\"><img src=\"/found/here\"/></a>\n</figure>", nil)
|
||||
}
|
||||
|
||||
// #1642
|
||||
func TestShortcodeWrappedInPIssue(t *testing.T) {
|
||||
t.Parallel()
|
||||
wt := func(tem tpl.TemplateManager) error {
|
||||
tem.AddTemplate("_internal/shortcodes/bug.html", `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`)
|
||||
return nil
|
||||
}
|
||||
CheckShortCodeMatch(t, `
|
||||
{{< bug >}}
|
||||
|
||||
{{< bug >}}
|
||||
`, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", wt)
|
||||
}
|
||||
|
||||
// #6866
|
||||
func TestShortcodeIncomplete(t *testing.T) {
|
||||
t.Parallel()
|
||||
CheckShortCodeMatchAndError(t, `{{< >}}`, ".*shortcode has no name.*", nil, true)
|
||||
}
|
||||
|
||||
func TestExtractShortcodes(t *testing.T) {
|
||||
b := newTestSitesBuilder(t).WithSimpleConfigFile()
|
||||
|
||||
@@ -368,13 +49,6 @@ title: "Shortcodes Galore!"
|
||||
|
||||
s := b.H.Sites[0]
|
||||
|
||||
/*errCheck := func(s string) func(name string, assert *require.Assertions, shortcode *shortcode, err error) {
|
||||
return func(name string, assert *require.Assertions, shortcode *shortcode, err error) {
|
||||
c.Assert(err, name, qt.Not(qt.IsNil))
|
||||
c.Assert(err.Error(), name, qt.Equals, s)
|
||||
}
|
||||
}*/
|
||||
|
||||
// Make it more regexp friendly
|
||||
strReplacer := strings.NewReplacer("[", "{", "]", "}")
|
||||
|
||||
@@ -433,216 +107,18 @@ title: "Shortcodes Galore!"
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
counter := 0
|
||||
placeholderFunc := func() string {
|
||||
counter++
|
||||
return fmt.Sprintf("HAHA%s-%dHBHB", shortcodePlaceholderPrefix, counter)
|
||||
}
|
||||
|
||||
p, err := pageparser.ParseMain(strings.NewReader(test.input), pageparser.Config{})
|
||||
c.Assert(err, qt.IsNil)
|
||||
handler := newShortcodeHandler(nil, s, placeholderFunc)
|
||||
handler := newShortcodeHandler(nil, s)
|
||||
iter := p.Iterator()
|
||||
|
||||
short, err := handler.extractShortcode(0, 0, iter)
|
||||
short, err := handler.extractShortcode(0, 0, p.Input(), iter)
|
||||
|
||||
test.check(c, short, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShortcodesInSite(t *testing.T) {
|
||||
baseURL := "http://foo/bar"
|
||||
|
||||
tests := []struct {
|
||||
contentPath string
|
||||
content string
|
||||
outFile string
|
||||
expected any
|
||||
}{
|
||||
{
|
||||
"sect/doc1.md", `a{{< b >}}c`,
|
||||
filepath.FromSlash("public/sect/doc1/index.html"), "<p>abc</p>\n",
|
||||
},
|
||||
// Issue #1642: Multiple shortcodes wrapped in P
|
||||
// Deliberately forced to pass even if they maybe shouldn't.
|
||||
{
|
||||
"sect/doc2.md", `a
|
||||
|
||||
{{< b >}}
|
||||
{{< c >}}
|
||||
{{< d >}}
|
||||
|
||||
e`,
|
||||
filepath.FromSlash("public/sect/doc2/index.html"),
|
||||
"<p>a</p>\n\n<p>b<br />\nc\nd</p>\n\n<p>e</p>\n",
|
||||
},
|
||||
{
|
||||
"sect/doc3.md", `a
|
||||
|
||||
{{< b >}}
|
||||
{{< c >}}
|
||||
|
||||
{{< d >}}
|
||||
|
||||
e`,
|
||||
filepath.FromSlash("public/sect/doc3/index.html"),
|
||||
"<p>a</p>\n\n<p>b<br />\nc</p>\n\nd\n\n<p>e</p>\n",
|
||||
},
|
||||
{
|
||||
"sect/doc4.md", `a
|
||||
{{< b >}}
|
||||
{{< b >}}
|
||||
{{< b >}}
|
||||
{{< b >}}
|
||||
{{< b >}}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
`,
|
||||
filepath.FromSlash("public/sect/doc4/index.html"),
|
||||
"<p>a\nb\nb\nb\nb\nb</p>\n",
|
||||
},
|
||||
// #2192 #2209: Shortcodes in markdown headers
|
||||
{
|
||||
"sect/doc5.md", `# {{< b >}}
|
||||
## {{% c %}}`,
|
||||
filepath.FromSlash("public/sect/doc5/index.html"), `-hbhb">b</h1>`,
|
||||
},
|
||||
// #2223 pygments
|
||||
{
|
||||
"sect/doc6.md", "\n```bash\nb = {{< b >}} c = {{% c %}}\n```\n",
|
||||
filepath.FromSlash("public/sect/doc6/index.html"),
|
||||
`<span class="nv">b</span>`,
|
||||
},
|
||||
// #2249
|
||||
{
|
||||
"sect/doc7.ad", `_Shortcodes:_ *b: {{< b >}} c: {{% c %}}*`,
|
||||
filepath.FromSlash("public/sect/doc7/index.html"),
|
||||
"<div class=\"paragraph\">\n<p><em>Shortcodes:</em> <strong>b: b c: c</strong></p>\n</div>\n",
|
||||
},
|
||||
{
|
||||
"sect/doc8.rst", `**Shortcodes:** *b: {{< b >}} c: {{% c %}}*`,
|
||||
filepath.FromSlash("public/sect/doc8/index.html"),
|
||||
"<div class=\"document\">\n\n\n<p><strong>Shortcodes:</strong> <em>b: b c: c</em></p>\n</div>",
|
||||
},
|
||||
|
||||
// Issue #1229: Menus not available in shortcode.
|
||||
{
|
||||
"sect/doc10.md", `---
|
||||
menu:
|
||||
main:
|
||||
identifier: 'parent'
|
||||
tags:
|
||||
- Menu
|
||||
---
|
||||
**Menus:** {{< menu >}}`,
|
||||
filepath.FromSlash("public/sect/doc10/index.html"),
|
||||
"<p><strong>Menus:</strong> 1</p>\n",
|
||||
},
|
||||
// Issue #2323: Taxonomies not available in shortcode.
|
||||
{
|
||||
"sect/doc11.md", `---
|
||||
tags:
|
||||
- Bugs
|
||||
menu:
|
||||
main:
|
||||
parent: 'parent'
|
||||
---
|
||||
**Tags:** {{< tags >}}`,
|
||||
filepath.FromSlash("public/sect/doc11/index.html"),
|
||||
"<p><strong>Tags:</strong> 2</p>\n",
|
||||
},
|
||||
{
|
||||
"sect/doc12.md", `---
|
||||
title: "Foo"
|
||||
---
|
||||
|
||||
{{% html-indented-v1 %}}`,
|
||||
"public/sect/doc12/index.html",
|
||||
"<h1>Hugo!</h1>",
|
||||
},
|
||||
}
|
||||
|
||||
temp := tests[:0]
|
||||
for _, test := range tests {
|
||||
if strings.HasSuffix(test.contentPath, ".ad") && !asciidocext.Supports() {
|
||||
t.Log("Skip Asciidoc test case as no Asciidoc present.")
|
||||
continue
|
||||
} else if strings.HasSuffix(test.contentPath, ".rst") && !rst.Supports() {
|
||||
t.Log("Skip Rst test case as no rst2html present.")
|
||||
continue
|
||||
}
|
||||
temp = append(temp, test)
|
||||
}
|
||||
tests = temp
|
||||
|
||||
sources := make([][2]string, len(tests))
|
||||
|
||||
for i, test := range tests {
|
||||
sources[i] = [2]string{filepath.FromSlash(test.contentPath), test.content}
|
||||
}
|
||||
|
||||
addTemplates := func(templ tpl.TemplateManager) error {
|
||||
templ.AddTemplate("_default/single.html", "{{.Content}} Word Count: {{ .WordCount }}")
|
||||
|
||||
templ.AddTemplate("_internal/shortcodes/b.html", `b`)
|
||||
templ.AddTemplate("_internal/shortcodes/c.html", `c`)
|
||||
templ.AddTemplate("_internal/shortcodes/d.html", `d`)
|
||||
templ.AddTemplate("_internal/shortcodes/html-indented-v1.html", "{{ $_hugo_config := `{ \"version\": 1 }` }}"+`
|
||||
<h1>Hugo!</h1>
|
||||
`)
|
||||
templ.AddTemplate("_internal/shortcodes/menu.html", `{{ len (index .Page.Menus "main").Children }}`)
|
||||
templ.AddTemplate("_internal/shortcodes/tags.html", `{{ len .Page.Site.Taxonomies.tags }}`)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg, fs := newTestCfg()
|
||||
|
||||
cfg.Set("defaultContentLanguage", "en")
|
||||
cfg.Set("baseURL", baseURL)
|
||||
cfg.Set("uglyURLs", false)
|
||||
cfg.Set("verbose", true)
|
||||
|
||||
cfg.Set("security", map[string]any{
|
||||
"exec": map[string]any{
|
||||
"allow": []string{"^python$", "^rst2html.*", "^asciidoctor$"},
|
||||
},
|
||||
})
|
||||
|
||||
cfg.Set("markup.highlight.noClasses", false)
|
||||
cfg.Set("markup.highlight.codeFences", true)
|
||||
cfg.Set("markup", map[string]any{
|
||||
"defaultMarkdownHandler": "blackfriday", // TODO(bep)
|
||||
})
|
||||
|
||||
writeSourcesToSource(t, "content", fs, sources...)
|
||||
|
||||
s := buildSingleSite(t, deps.DepsCfg{WithTemplate: addTemplates, Fs: fs, Cfg: cfg}, BuildCfg{})
|
||||
|
||||
for i, test := range tests {
|
||||
test := test
|
||||
t.Run(fmt.Sprintf("test=%d;contentPath=%s", i, test.contentPath), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
th := newTestHelper(s.Cfg, s.Fs, t)
|
||||
|
||||
expected := cast.ToStringSlice(test.expected)
|
||||
|
||||
th.assertFileContent(filepath.FromSlash(test.outFile), expected...)
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestShortcodeMultipleOutputFormats(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -815,6 +291,58 @@ func BenchmarkReplaceShortcodeTokens(b *testing.B) {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkShortcodesInSite(b *testing.B) {
|
||||
files := `
|
||||
-- config.toml --
|
||||
-- layouts/shortcodes/mark1.md --
|
||||
{{ .Inner }}
|
||||
-- layouts/shortcodes/mark2.md --
|
||||
1. Item Mark2 1
|
||||
1. Item Mark2 2
|
||||
1. Item Mark2 2-1
|
||||
1. Item Mark2 3
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Content }}
|
||||
`
|
||||
|
||||
content := `
|
||||
---
|
||||
title: "Markdown Shortcode"
|
||||
---
|
||||
|
||||
## List
|
||||
|
||||
1. List 1
|
||||
{{§ mark1 §}}
|
||||
1. Item Mark1 1
|
||||
1. Item Mark1 2
|
||||
{{§ mark2 §}}
|
||||
{{§ /mark1 §}}
|
||||
|
||||
`
|
||||
|
||||
for i := 1; i < 100; i++ {
|
||||
files += fmt.Sprintf("\n-- content/posts/p%d.md --\n"+content, i+1)
|
||||
}
|
||||
files = strings.ReplaceAll(files, "§", "%")
|
||||
|
||||
cfg := IntegrationTestConfig{
|
||||
T: b,
|
||||
TxtarString: files,
|
||||
}
|
||||
builders := make([]*IntegrationTestBuilder, b.N)
|
||||
|
||||
for i := range builders {
|
||||
builders[i] = NewIntegrationTestBuilder(cfg)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
builders[i].Build()
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceShortcodeTokens(t *testing.T) {
|
||||
t.Parallel()
|
||||
for i, this := range []struct {
|
||||
@@ -1235,7 +763,7 @@ title: "Hugo Rocks!"
|
||||
)
|
||||
}
|
||||
|
||||
func TestShortcodeTypedParams(t *testing.T) {
|
||||
func TestShortcodeParams(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := qt.New(t)
|
||||
|
||||
@@ -1250,6 +778,7 @@ title: "Hugo Rocks!"
|
||||
types positional: {{< hello true false 33 3.14 >}}
|
||||
types named: {{< hello b1=true b2=false i1=33 f1=3.14 >}}
|
||||
types string: {{< hello "true" trues "33" "3.14" >}}
|
||||
escaped quoute: {{< hello "hello \"world\"." >}}
|
||||
|
||||
|
||||
`).WithTemplatesAdded(
|
||||
@@ -1268,28 +797,20 @@ Get: {{ printf "%v (%T)" $b1 $b1 | safeHTML }}
|
||||
"types positional: - 0: true (bool) - 1: false (bool) - 2: 33 (int) - 3: 3.14 (float64)",
|
||||
"types named: - b1: true (bool) - b2: false (bool) - f1: 3.14 (float64) - i1: 33 (int) Get: true (bool) ",
|
||||
"types string: - 0: true (string) - 1: trues (string) - 2: 33 (string) - 3: 3.14 (string) ",
|
||||
"hello "world". (string)",
|
||||
)
|
||||
}
|
||||
|
||||
func TestShortcodeRef(t *testing.T) {
|
||||
for _, plainIDAnchors := range []bool{false, true} {
|
||||
plainIDAnchors := plainIDAnchors
|
||||
t.Run(fmt.Sprintf("plainIDAnchors=%t", plainIDAnchors), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Parallel()
|
||||
|
||||
v := config.NewWithTestDefaults()
|
||||
v.Set("baseURL", "https://example.org")
|
||||
v.Set("blackfriday", map[string]any{
|
||||
"plainIDAnchors": plainIDAnchors,
|
||||
})
|
||||
v.Set("markup", map[string]any{
|
||||
"defaultMarkdownHandler": "blackfriday", // TODO(bep)
|
||||
})
|
||||
v := config.NewWithTestDefaults()
|
||||
v.Set("baseURL", "https://example.org")
|
||||
|
||||
builder := newTestSitesBuilder(t).WithViper(v)
|
||||
builder := newTestSitesBuilder(t).WithViper(v)
|
||||
|
||||
for i := 1; i <= 2; i++ {
|
||||
builder.WithContent(fmt.Sprintf("page%d.md", i), `---
|
||||
for i := 1; i <= 2; i++ {
|
||||
builder.WithContent(fmt.Sprintf("page%d.md", i), `---
|
||||
title: "Hugo Rocks!"
|
||||
---
|
||||
|
||||
@@ -1305,33 +826,19 @@ title: "Hugo Rocks!"
|
||||
|
||||
|
||||
`)
|
||||
}
|
||||
}
|
||||
|
||||
builder.Build(BuildCfg{})
|
||||
builder.Build(BuildCfg{})
|
||||
|
||||
if plainIDAnchors {
|
||||
builder.AssertFileContent("public/page2/index.html",
|
||||
`
|
||||
builder.AssertFileContent("public/page2/index.html", `
|
||||
<a href="/page1/#doc">Page 1 with anchor</a>
|
||||
<a href="https://example.org/page2/">Page 2</a>
|
||||
<a href="/page2/#doc">Page 2 with anchor</a></p>
|
||||
|
||||
<h2 id="doc">Doc</h2>
|
||||
`,
|
||||
)
|
||||
} else {
|
||||
builder.AssertFileContent("public/page2/index.html",
|
||||
`
|
||||
<p><a href="https://example.org/page1/">Page 1</a>
|
||||
<a href="/page1/#doc:45ca767ba77bc1445a0acab74f80812f">Page 1 with anchor</a>
|
||||
<a href="https://example.org/page2/">Page 2</a>
|
||||
<a href="/page2/#doc:8e3cdf52fa21e33270c99433820e46bd">Page 2 with anchor</a></p>
|
||||
<h2 id="doc:8e3cdf52fa21e33270c99433820e46bd">Doc</h2>
|
||||
`,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
// https://github.com/gohugoio/hugo/issues/6857
|
||||
@@ -1398,3 +905,153 @@ outputs: ["html", "css", "csv", "json"]
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// #9821
|
||||
func TestShortcodeMarkdownOutputFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- config.toml --
|
||||
-- content/p1.md --
|
||||
---
|
||||
title: "p1"
|
||||
---
|
||||
{{< foo >}}
|
||||
-- layouts/shortcodes/foo.md --
|
||||
§§§
|
||||
<x
|
||||
§§§
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Content }}
|
||||
`
|
||||
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
Running: true,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/p1/index.html", `
|
||||
<x
|
||||
`)
|
||||
|
||||
}
|
||||
|
||||
func TestShortcodePreserveIndentation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- config.toml --
|
||||
-- content/p1.md --
|
||||
---
|
||||
title: "p1"
|
||||
---
|
||||
|
||||
## List With Indented Shortcodes
|
||||
|
||||
1. List 1
|
||||
{{% mark1 %}}
|
||||
1. Item Mark1 1
|
||||
1. Item Mark1 2
|
||||
{{% mark2 %}}
|
||||
{{% /mark1 %}}
|
||||
-- layouts/shortcodes/mark1.md --
|
||||
{{ .Inner }}
|
||||
-- layouts/shortcodes/mark2.md --
|
||||
1. Item Mark2 1
|
||||
1. Item Mark2 2
|
||||
1. Item Mark2 2-1
|
||||
1. Item Mark2 3
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Content }}
|
||||
`
|
||||
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
Running: true,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/p1/index.html", "<ol>\n<li>\n<p>List 1</p>\n<ol>\n<li>Item Mark1 1</li>\n<li>Item Mark1 2</li>\n<li>Item Mark2 1</li>\n<li>Item Mark2 2\n<ol>\n<li>Item Mark2 2-1</li>\n</ol>\n</li>\n<li>Item Mark2 3</li>\n</ol>\n</li>\n</ol>")
|
||||
|
||||
}
|
||||
|
||||
func TestShortcodeCodeblockIndent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- config.toml --
|
||||
-- content/p1.md --
|
||||
---
|
||||
title: "p1"
|
||||
---
|
||||
|
||||
## Code block
|
||||
|
||||
{{% code %}}
|
||||
|
||||
-- layouts/shortcodes/code.md --
|
||||
echo "foo";
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Content }}
|
||||
`
|
||||
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
Running: true,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/p1/index.html", "<pre><code>echo "foo";\n</code></pre>")
|
||||
|
||||
}
|
||||
|
||||
func TestShortcodeHighlightDeindent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- config.toml --
|
||||
[markup]
|
||||
[markup.highlight]
|
||||
codeFences = true
|
||||
noClasses = false
|
||||
-- content/p1.md --
|
||||
---
|
||||
title: "p1"
|
||||
---
|
||||
|
||||
## Indent 5 Spaces
|
||||
|
||||
{{< highlight bash >}}
|
||||
line 1;
|
||||
line 2;
|
||||
line 3;
|
||||
{{< /highlight >}}
|
||||
|
||||
-- layouts/_default/single.html --
|
||||
{{ .Content }}
|
||||
`
|
||||
|
||||
b := NewIntegrationTestBuilder(
|
||||
IntegrationTestConfig{
|
||||
T: t,
|
||||
TxtarString: files,
|
||||
Running: true,
|
||||
},
|
||||
).Build()
|
||||
|
||||
b.AssertFileContent("public/p1/index.html", `
|
||||
<pre><code> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">line 1<span class="p">;</span>
|
||||
</span></span><span class="line"><span class="cl">line 2<span class="p">;</span>
|
||||
</span></span><span class="line"><span class="cl">line 3<span class="p">;</span></span></span></code></pre></div>
|
||||
</code></pre>
|
||||
|
||||
`)
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user