mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
Add Markdown diagrams and render hooks for code blocks
You can now create custom hook templates for code blocks, either one for all (`render-codeblock.html`) or for a given code language (e.g. `render-codeblock-go.html`). We also used this new hook to add support for diagrams in Hugo: * Goat (Go ASCII Tool) is built-in and enabled by default; just create a fenced code block with the language `goat` and start draw your Ascii diagrams. * Another popular alternative for diagrams in Markdown, Mermaid (supported by GitHub), can also be implemented with a simple template. See the Hugo documentation for more information. Updates #7765 Closes #9538 Fixes #9553 Fixes #8520 Fixes #6702 Fixes #9558
This commit is contained in:
parent
2c20f5bc00
commit
08fdca9d93
73 changed files with 1887 additions and 1986 deletions
|
@ -18,6 +18,14 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// As implemented by strings.Builder.
|
||||||
|
type FlexiWriter interface {
|
||||||
|
io.Writer
|
||||||
|
io.ByteWriter
|
||||||
|
WriteString(s string) (int, error)
|
||||||
|
WriteRune(r rune) (int, error)
|
||||||
|
}
|
||||||
|
|
||||||
type multiWriteCloser struct {
|
type multiWriteCloser struct {
|
||||||
io.Writer
|
io.Writer
|
||||||
closers []io.WriteCloser
|
closers []io.WriteCloser
|
||||||
|
|
|
@ -66,6 +66,14 @@
|
||||||
|
|
||||||
{{ block "footer" . }}{{ partialCached "site-footer.html" . }}{{ end }}
|
{{ block "footer" . }}{{ partialCached "site-footer.html" . }}{{ end }}
|
||||||
|
|
||||||
|
{{ if .Page.Store.Get "hasMermaid" }}
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
|
||||||
|
<script>
|
||||||
|
mermaid.initialize({ startOnLoad: true });
|
||||||
|
</script>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
217
docs/content/en/content-management/diagrams.md
Normal file
217
docs/content/en/content-management/diagrams.md
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
---
|
||||||
|
title: Diagrams
|
||||||
|
date: 2022-02-20
|
||||||
|
categories: [content management]
|
||||||
|
keywords: [diagrams,drawing]
|
||||||
|
menu:
|
||||||
|
docs:
|
||||||
|
parent: "content-management"
|
||||||
|
weight: 22
|
||||||
|
weight: 22
|
||||||
|
toc: true
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
## Mermaid Diagrams
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant Alice
|
||||||
|
participant Bob
|
||||||
|
Alice->>John: Hello John, how are you?
|
||||||
|
loop Healthcheck
|
||||||
|
John->>John: Fight against hypochondria
|
||||||
|
end
|
||||||
|
Note right of John: Rational thoughts <br/>prevail!
|
||||||
|
John-->>Alice: Great!
|
||||||
|
John->>Bob: How about you?
|
||||||
|
Bob-->>John: Jolly good!
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Goat Ascii Diagram Examples
|
||||||
|
|
||||||
|
### Graphics
|
||||||
|
|
||||||
|
```goat
|
||||||
|
.
|
||||||
|
0 3 P * Eye / ^ /
|
||||||
|
*-------* +y \ +) \ / Reflection
|
||||||
|
1 /| 2 /| ^ \ \ \ v
|
||||||
|
*-------* | | v0 \ v3 --------*--------
|
||||||
|
| |4 | |7 | *----\-----*
|
||||||
|
| *-----|-* +-----> +x / v X \ .-.<-------- o
|
||||||
|
|/ |/ / / o \ | / | Refraction / \
|
||||||
|
*-------* v / \ +-' / \
|
||||||
|
5 6 +z v1 *------------------* v2 | o-----o
|
||||||
|
v
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Complex
|
||||||
|
|
||||||
|
```goat
|
||||||
|
+-------------------+ ^ .---.
|
||||||
|
| A Box |__.--.__ __.--> | .-. | |
|
||||||
|
| | '--' v | * |<--- | |
|
||||||
|
+-------------------+ '-' | |
|
||||||
|
Round *---(-. |
|
||||||
|
.-----------------. .-------. .----------. .-------. | | |
|
||||||
|
| Mixed Rounded | | | / Diagonals \ | | | | | |
|
||||||
|
| & Square Corners | '--. .--' / \ |---+---| '-)-' .--------.
|
||||||
|
'--+------------+-' .--. | '-------+--------' | | | | / Search /
|
||||||
|
| | | | '---. | '-------' | '-+------'
|
||||||
|
|<---------->| | | | v Interior | ^
|
||||||
|
' <---' '----' .-----------. ---. .--- v |
|
||||||
|
.------------------. Diag line | .-------. +---. \ / . |
|
||||||
|
| if (a > b) +---. .--->| | | | | Curved line \ / / \ |
|
||||||
|
| obj->fcn() | \ / | '-------' |<--' + / \ |
|
||||||
|
'------------------' '--' '--+--------' .--. .--. | .-. +Done?+-'
|
||||||
|
.---+-----. | ^ |\ | | /| .--+ | | \ /
|
||||||
|
| | | Join \|/ | | Curved | \| |/ | | \ | \ /
|
||||||
|
| | +----> o --o-- '-' Vertical '--' '--' '-- '--' + .---.
|
||||||
|
<--+---+-----' | /|\ | | 3 |
|
||||||
|
v not:line 'quotes' .-' '---'
|
||||||
|
.-. .---+--------. / A || B *bold* | ^
|
||||||
|
| | | Not a dot | <---+---<-- A dash--is not a line v |
|
||||||
|
'-' '---------+--' / Nor/is this. ---
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Process
|
||||||
|
|
||||||
|
```goat
|
||||||
|
.
|
||||||
|
.---------. / \
|
||||||
|
| START | / \ .-+-------+-. ___________
|
||||||
|
'----+----' .-------. A / \ B | |COMPLEX| | / \ .-.
|
||||||
|
| | END |<-----+CHOICE +----->| | | +--->+ PREPARATION +--->| X |
|
||||||
|
v '-------' \ / | |PROCESS| | \___________/ '-'
|
||||||
|
.---------. \ / '-+---+---+-'
|
||||||
|
/ INPUT / \ /
|
||||||
|
'-----+---' '
|
||||||
|
| ^
|
||||||
|
v |
|
||||||
|
.-----------. .-----+-----. .-.
|
||||||
|
| PROCESS +---------------->| PROCESS |<------+ X |
|
||||||
|
'-----------' '-----------' '-'
|
||||||
|
```
|
||||||
|
|
||||||
|
### File tree
|
||||||
|
|
||||||
|
Created from https://arthursonzogni.com/Diagon/#Tree
|
||||||
|
|
||||||
|
```goat { width=300 color="orange" }
|
||||||
|
───Linux─┬─Android
|
||||||
|
├─Debian─┬─Ubuntu─┬─Lubuntu
|
||||||
|
│ │ ├─Kubuntu
|
||||||
|
│ │ ├─Xubuntu
|
||||||
|
│ │ └─Xubuntu
|
||||||
|
│ └─Mint
|
||||||
|
├─Centos
|
||||||
|
└─Fedora
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Sequence Diagram
|
||||||
|
|
||||||
|
https://arthursonzogni.com/Diagon/#Sequence
|
||||||
|
|
||||||
|
```goat { class="w-40" }
|
||||||
|
┌─────┐ ┌───┐
|
||||||
|
│Alice│ │Bob│
|
||||||
|
└──┬──┘ └─┬─┘
|
||||||
|
│ │
|
||||||
|
│ Hello Bob! │
|
||||||
|
│───────────>│
|
||||||
|
│ │
|
||||||
|
│Hello Alice!│
|
||||||
|
│<───────────│
|
||||||
|
┌──┴──┐ ┌─┴─┐
|
||||||
|
│Alice│ │Bob│
|
||||||
|
└─────┘ └───┘
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Flowchart
|
||||||
|
|
||||||
|
https://arthursonzogni.com/Diagon/#Flowchart
|
||||||
|
|
||||||
|
```goat
|
||||||
|
_________________
|
||||||
|
╱ ╲ ┌─────┐
|
||||||
|
╱ DO YOU UNDERSTAND ╲____________________________________________________│GOOD!│
|
||||||
|
╲ FLOW CHARTS? ╱yes └──┬──┘
|
||||||
|
╲_________________╱ │
|
||||||
|
│no │
|
||||||
|
_________▽_________ ______________________ │
|
||||||
|
╱ ╲ ╱ ╲ ┌────┐ │
|
||||||
|
╱ OKAY, YOU SEE THE ╲________________╱ ... AND YOU CAN SEE ╲___│GOOD│ │
|
||||||
|
╲ LINE LABELED 'YES'? ╱yes ╲ THE ONES LABELED 'NO'? ╱yes└──┬─┘ │
|
||||||
|
╲___________________╱ ╲______________________╱ │ │
|
||||||
|
│no │no │ │
|
||||||
|
________▽_________ _________▽__________ │ │
|
||||||
|
╱ ╲ ┌───────────┐ ╱ ╲ │ │
|
||||||
|
╱ BUT YOU SEE THE ╲___│WAIT, WHAT?│ ╱ BUT YOU JUST ╲___ │ │
|
||||||
|
╲ ONES LABELED 'NO'? ╱yes└───────────┘ ╲ FOLLOWED THEM TWICE? ╱yes│ │ │
|
||||||
|
╲__________________╱ ╲____________________╱ │ │ │
|
||||||
|
│no │no │ │ │
|
||||||
|
┌───▽───┐ │ │ │ │
|
||||||
|
│LISTEN.│ └───────┬───────┘ │ │
|
||||||
|
└───┬───┘ ┌──────▽─────┐ │ │
|
||||||
|
┌─────▽────┐ │(THAT WASN'T│ │ │
|
||||||
|
│I HATE YOU│ │A QUESTION) │ │ │
|
||||||
|
└──────────┘ └──────┬─────┘ │ │
|
||||||
|
┌────▽───┐ │ │
|
||||||
|
│SCREW IT│ │ │
|
||||||
|
└────┬───┘ │ │
|
||||||
|
└─────┬─────┘ │
|
||||||
|
│ │
|
||||||
|
└─────┬─────┘
|
||||||
|
┌───────▽──────┐
|
||||||
|
│LET'S GO DRING│
|
||||||
|
└───────┬──────┘
|
||||||
|
┌─────────▽─────────┐
|
||||||
|
│HEY, I SHOULD TRY │
|
||||||
|
│INSTALLING FREEBSD!│
|
||||||
|
└───────────────────┘
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Table
|
||||||
|
|
||||||
|
https://arthursonzogni.com/Diagon/#Table
|
||||||
|
|
||||||
|
```goat { class="w-80 dark-blue" }
|
||||||
|
┌────────────────────────────────────────────────┐
|
||||||
|
│ │
|
||||||
|
├────────────────────────────────────────────────┤
|
||||||
|
│SYNTAX = { PRODUCTION } . │
|
||||||
|
├────────────────────────────────────────────────┤
|
||||||
|
│PRODUCTION = IDENTIFIER "=" EXPRESSION "." . │
|
||||||
|
├────────────────────────────────────────────────┤
|
||||||
|
│EXPRESSION = TERM { "|" TERM } . │
|
||||||
|
├────────────────────────────────────────────────┤
|
||||||
|
│TERM = FACTOR { FACTOR } . │
|
||||||
|
├────────────────────────────────────────────────┤
|
||||||
|
│FACTOR = IDENTIFIER │
|
||||||
|
├────────────────────────────────────────────────┤
|
||||||
|
│ | LITERAL │
|
||||||
|
├────────────────────────────────────────────────┤
|
||||||
|
│ | "[" EXPRESSION "]" │
|
||||||
|
├────────────────────────────────────────────────┤
|
||||||
|
│ | "(" EXPRESSION ")" │
|
||||||
|
├────────────────────────────────────────────────┤
|
||||||
|
│ | "{" EXPRESSION "}" . │
|
||||||
|
├────────────────────────────────────────────────┤
|
||||||
|
│IDENTIFIER = letter { letter } . │
|
||||||
|
├────────────────────────────────────────────────┤
|
||||||
|
│LITERAL = """" character { character } """" .│
|
||||||
|
└────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
18
docs/layouts/_default/_markup/render-codeblock-goat.html
Normal file
18
docs/layouts/_default/_markup/render-codeblock-goat.html
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{{ $width := .Attributes.width }}
|
||||||
|
{{ $height := .Attributes.height }}
|
||||||
|
{{ $class := .Attributes.class | default "" }}
|
||||||
|
<div class="goat svg-container {{ $class }}">
|
||||||
|
{{ with diagrams.Goat .Code }}
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
font-family="Menlo,Lucida Console,monospace"
|
||||||
|
{{ if or $width $height }}
|
||||||
|
{{ with $width }}width="{{ . }}"{{ end }}
|
||||||
|
{{ with $height }}height="{{ . }}"{{ end }}
|
||||||
|
{{ else }}
|
||||||
|
viewBox="0 0 {{ .Width }} {{ .Height }}"
|
||||||
|
{{ end }}>
|
||||||
|
{{ .Body }}
|
||||||
|
</svg>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
|
@ -0,0 +1,4 @@
|
||||||
|
<div class="mermaid">
|
||||||
|
{{- .Code | safeHTML }}
|
||||||
|
</div>
|
||||||
|
{{ .Page.Store.Set "hasMermaid" true }}
|
5
go.mod
5
go.mod
|
@ -9,6 +9,7 @@ require (
|
||||||
github.com/aws/aws-sdk-go v1.43.5
|
github.com/aws/aws-sdk-go v1.43.5
|
||||||
github.com/bep/debounce v1.2.0
|
github.com/bep/debounce v1.2.0
|
||||||
github.com/bep/gitmap v1.1.2
|
github.com/bep/gitmap v1.1.2
|
||||||
|
github.com/bep/goat v0.5.0
|
||||||
github.com/bep/godartsass v0.12.0
|
github.com/bep/godartsass v0.12.0
|
||||||
github.com/bep/golibsass v1.0.0
|
github.com/bep/golibsass v1.0.0
|
||||||
github.com/bep/gowebp v0.1.0
|
github.com/bep/gowebp v0.1.0
|
||||||
|
@ -19,7 +20,7 @@ require (
|
||||||
github.com/dustin/go-humanize v1.0.0
|
github.com/dustin/go-humanize v1.0.0
|
||||||
github.com/evanw/esbuild v0.14.22
|
github.com/evanw/esbuild v0.14.22
|
||||||
github.com/fortytw2/leaktest v1.3.0
|
github.com/fortytw2/leaktest v1.3.0
|
||||||
github.com/frankban/quicktest v1.14.0
|
github.com/frankban/quicktest v1.14.2
|
||||||
github.com/fsnotify/fsnotify v1.5.1
|
github.com/fsnotify/fsnotify v1.5.1
|
||||||
github.com/getkin/kin-openapi v0.85.0
|
github.com/getkin/kin-openapi v0.85.0
|
||||||
github.com/ghodss/yaml v1.0.0
|
github.com/ghodss/yaml v1.0.0
|
||||||
|
@ -57,7 +58,7 @@ require (
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/tdewolff/minify/v2 v2.9.29
|
github.com/tdewolff/minify/v2 v2.9.29
|
||||||
github.com/yuin/goldmark v1.4.7
|
github.com/yuin/goldmark v1.4.7
|
||||||
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691
|
go.uber.org/atomic v1.9.0
|
||||||
gocloud.dev v0.20.0
|
gocloud.dev v0.20.0
|
||||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410
|
golang.org/x/image v0.0.0-20211028202545-6944b10bf410
|
||||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
|
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -144,6 +144,10 @@ github.com/bep/debounce v1.2.0 h1:wXds8Kq8qRfwAOpAxHrJDbCXgC5aHSzgQb/0gKsHQqo=
|
||||||
github.com/bep/debounce v1.2.0/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
github.com/bep/debounce v1.2.0/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
|
||||||
github.com/bep/gitmap v1.1.2 h1:zk04w1qc1COTZPPYWDQHvns3y1afOsdRfraFQ3qI840=
|
github.com/bep/gitmap v1.1.2 h1:zk04w1qc1COTZPPYWDQHvns3y1afOsdRfraFQ3qI840=
|
||||||
github.com/bep/gitmap v1.1.2/go.mod h1:g9VRETxFUXNWzMiuxOwcudo6DfZkW9jOsOW0Ft4kYaY=
|
github.com/bep/gitmap v1.1.2/go.mod h1:g9VRETxFUXNWzMiuxOwcudo6DfZkW9jOsOW0Ft4kYaY=
|
||||||
|
github.com/bep/goat v0.0.0-20220222160823-cc97a132eb5e h1:On3hMv9ffG+0fgPIjKPXiFu5QVS9jM1Vzr5/ghmSLy4=
|
||||||
|
github.com/bep/goat v0.0.0-20220222160823-cc97a132eb5e/go.mod h1:Md9x7gRxiWKs85yHlVTvHQw9rg86Bm+Y4SuYE8CTH7c=
|
||||||
|
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.12.0 h1:VvGLA4XpXUjKvp53SI05YFLhRFJ78G+Ybnlaz6Oul7E=
|
github.com/bep/godartsass v0.12.0 h1:VvGLA4XpXUjKvp53SI05YFLhRFJ78G+Ybnlaz6Oul7E=
|
||||||
github.com/bep/godartsass v0.12.0/go.mod h1:nXQlHHk4H1ghUk6n/JkYKG5RD43yJfcfp5aHRqT/pc4=
|
github.com/bep/godartsass v0.12.0/go.mod h1:nXQlHHk4H1ghUk6n/JkYKG5RD43yJfcfp5aHRqT/pc4=
|
||||||
github.com/bep/golibsass v1.0.0 h1:gNguBMSDi5yZEZzVZP70YpuFQE3qogJIGUlrVILTmOw=
|
github.com/bep/golibsass v1.0.0 h1:gNguBMSDi5yZEZzVZP70YpuFQE3qogJIGUlrVILTmOw=
|
||||||
|
@ -239,6 +243,8 @@ github.com/frankban/quicktest v1.11.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P
|
||||||
github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU=
|
github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU=
|
||||||
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
|
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
|
||||||
github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
|
github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og=
|
||||||
|
github.com/frankban/quicktest v1.14.2 h1:SPb1KFFmM+ybpEjPUhCCkZOM5xlovT5UbrMvWnXyBns=
|
||||||
|
github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
||||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||||
|
@ -623,6 +629,8 @@ go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
|
||||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||||
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||||
gocloud.dev v0.20.0 h1:mbEKMfnyPV7W1Rj35R1xXfjszs9dXkwSOq2KoFr25g8=
|
gocloud.dev v0.20.0 h1:mbEKMfnyPV7W1Rj35R1xXfjszs9dXkwSOq2KoFr25g8=
|
||||||
|
|
|
@ -30,6 +30,7 @@ import (
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/markup/converter"
|
"github.com/gohugoio/hugo/markup/converter"
|
||||||
|
"github.com/gohugoio/hugo/markup/converter/hooks"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/markup"
|
"github.com/gohugoio/hugo/markup"
|
||||||
|
|
||||||
|
@ -47,8 +48,8 @@ var (
|
||||||
// ContentSpec provides functionality to render markdown content.
|
// ContentSpec provides functionality to render markdown content.
|
||||||
type ContentSpec struct {
|
type ContentSpec struct {
|
||||||
Converters markup.ConverterProvider
|
Converters markup.ConverterProvider
|
||||||
MardownConverter converter.Converter // Markdown converter with no document context
|
|
||||||
anchorNameSanitizer converter.AnchorNameSanitizer
|
anchorNameSanitizer converter.AnchorNameSanitizer
|
||||||
|
getRenderer func(t hooks.RendererType, id interface{}) interface{}
|
||||||
|
|
||||||
// SummaryLength is the length of the summary that Hugo extracts from a content.
|
// SummaryLength is the length of the summary that Hugo extracts from a content.
|
||||||
summaryLength int
|
summaryLength int
|
||||||
|
@ -88,7 +89,6 @@ func NewContentSpec(cfg config.Provider, logger loggers.Logger, contentFs afero.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
spec.MardownConverter = conv
|
|
||||||
if as, ok := conv.(converter.AnchorNameSanitizer); ok {
|
if as, ok := conv.(converter.AnchorNameSanitizer); ok {
|
||||||
spec.anchorNameSanitizer = as
|
spec.anchorNameSanitizer = as
|
||||||
} else {
|
} else {
|
||||||
|
@ -192,14 +192,6 @@ func ExtractTOC(content []byte) (newcontent []byte, toc []byte) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ContentSpec) RenderMarkdown(src []byte) ([]byte, error) {
|
|
||||||
b, err := c.MardownConverter.Convert(converter.RenderContext{Src: src})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return b.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ContentSpec) SanitizeAnchorName(s string) string {
|
func (c *ContentSpec) SanitizeAnchorName(s string) string {
|
||||||
return c.anchorNameSanitizer.SanitizeAnchorName(s)
|
return c.anchorNameSanitizer.SanitizeAnchorName(s)
|
||||||
}
|
}
|
||||||
|
|
|
@ -231,8 +231,8 @@ SHORT3|
|
||||||
b.AssertFileContent("public/blog/p3/index.html", `PARTIAL3`)
|
b.AssertFileContent("public/blog/p3/index.html", `PARTIAL3`)
|
||||||
// We may add type template support later, keep this for then. b.AssertFileContent("public/docs/docs1/index.html", `Link docs section: Docs 1|END`)
|
// We may add type template support later, keep this for then. b.AssertFileContent("public/docs/docs1/index.html", `Link docs section: Docs 1|END`)
|
||||||
b.AssertFileContent("public/blog/p4/index.html", `<p>IMAGE: Cool Page With Image||/images/Dragster.jpg|Title: image title|Text: Drag Racing|END</p>`)
|
b.AssertFileContent("public/blog/p4/index.html", `<p>IMAGE: Cool Page With Image||/images/Dragster.jpg|Title: image title|Text: Drag Racing|END</p>`)
|
||||||
// The regular markdownify func currently gets regular links.
|
// markdownify
|
||||||
b.AssertFileContent("public/blog/p5/index.html", "Inner Link: <a href=\"https://www.google.com\" title=\"Google's Homepage\">Inner Link</a>\n</div>")
|
b.AssertFileContent("public/blog/p5/index.html", "Inner Link: |https://www.google.com|Title: Google's Homepage|Text: Inner Link|END")
|
||||||
|
|
||||||
b.AssertFileContent("public/blog/p6/index.html",
|
b.AssertFileContent("public/blog/p6/index.html",
|
||||||
"Inner Inline: Inner Link: With RenderString|https://www.gohugo.io|Title: Hugo's Homepage|Text: Inner Link|END",
|
"Inner Inline: Inner Link: With RenderString|https://www.gohugo.io|Title: Hugo's Homepage|Text: Inner Link|END",
|
||||||
|
|
|
@ -125,7 +125,7 @@ func (s *IntegrationTestBuilder) AssertFileContent(filename string, matches ...s
|
||||||
if match == "" || strings.HasPrefix(match, "#") {
|
if match == "" || strings.HasPrefix(match, "#") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
s.Assert(content, qt.Contains, match, qt.Commentf(content))
|
s.Assert(content, qt.Contains, match, qt.Commentf(m))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,7 +164,7 @@ func (s *IntegrationTestBuilder) AssertRenderCountPage(count int) {
|
||||||
func (s *IntegrationTestBuilder) Build() *IntegrationTestBuilder {
|
func (s *IntegrationTestBuilder) Build() *IntegrationTestBuilder {
|
||||||
s.Helper()
|
s.Helper()
|
||||||
_, err := s.BuildE()
|
_, err := s.BuildE()
|
||||||
if s.Cfg.Verbose {
|
if s.Cfg.Verbose || err != nil {
|
||||||
fmt.Println(s.logBuff.String())
|
fmt.Println(s.logBuff.String())
|
||||||
}
|
}
|
||||||
s.Assert(err, qt.IsNil)
|
s.Assert(err, qt.IsNil)
|
||||||
|
|
|
@ -314,7 +314,7 @@ Content.
|
||||||
nnSect := nnSite.getPage(page.KindSection, "sect")
|
nnSect := nnSite.getPage(page.KindSection, "sect")
|
||||||
c.Assert(nnSect, qt.Not(qt.IsNil))
|
c.Assert(nnSect, qt.Not(qt.IsNil))
|
||||||
c.Assert(len(nnSect.Pages()), qt.Equals, 12)
|
c.Assert(len(nnSect.Pages()), qt.Equals, 12)
|
||||||
nnHome, _ := nnSite.Info.Home()
|
nnHome := nnSite.Info.Home()
|
||||||
c.Assert(nnHome.RelPermalink(), qt.Equals, "/nn/")
|
c.Assert(nnHome.RelPermalink(), qt.Equals, "/nn/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,8 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"go.uber.org/atomic"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/identity"
|
"github.com/gohugoio/hugo/identity"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/markup/converter"
|
"github.com/gohugoio/hugo/markup/converter"
|
||||||
|
@ -47,7 +49,6 @@ import (
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/collections"
|
"github.com/gohugoio/hugo/common/collections"
|
||||||
"github.com/gohugoio/hugo/common/text"
|
"github.com/gohugoio/hugo/common/text"
|
||||||
"github.com/gohugoio/hugo/markup/converter/hooks"
|
|
||||||
"github.com/gohugoio/hugo/resources"
|
"github.com/gohugoio/hugo/resources"
|
||||||
"github.com/gohugoio/hugo/resources/page"
|
"github.com/gohugoio/hugo/resources/page"
|
||||||
"github.com/gohugoio/hugo/resources/resource"
|
"github.com/gohugoio/hugo/resources/resource"
|
||||||
|
@ -118,6 +119,9 @@ type pageState struct {
|
||||||
// formats (for all sites).
|
// formats (for all sites).
|
||||||
pageOutputs []*pageOutput
|
pageOutputs []*pageOutput
|
||||||
|
|
||||||
|
// Used to determine if we can reuse content across output formats.
|
||||||
|
pageOutputTemplateVariationsState *atomic.Uint32
|
||||||
|
|
||||||
// This will be shifted out when we start to render a new output format.
|
// This will be shifted out when we start to render a new output format.
|
||||||
*pageOutput
|
*pageOutput
|
||||||
|
|
||||||
|
@ -125,6 +129,10 @@ type pageState struct {
|
||||||
*pageCommon
|
*pageCommon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *pageState) reusePageOutputContent() bool {
|
||||||
|
return p.pageOutputTemplateVariationsState.Load() == 1
|
||||||
|
}
|
||||||
|
|
||||||
func (p *pageState) Err() error {
|
func (p *pageState) Err() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -394,56 +402,6 @@ func (ps *pageState) initCommonProviders(pp pagePaths) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pageState) createRenderHooks(f output.Format) (hooks.Renderers, error) {
|
|
||||||
layoutDescriptor := p.getLayoutDescriptor()
|
|
||||||
layoutDescriptor.RenderingHook = true
|
|
||||||
layoutDescriptor.LayoutOverride = false
|
|
||||||
layoutDescriptor.Layout = ""
|
|
||||||
|
|
||||||
var renderers hooks.Renderers
|
|
||||||
|
|
||||||
layoutDescriptor.Kind = "render-link"
|
|
||||||
templ, templFound, err := p.s.Tmpl().LookupLayout(layoutDescriptor, f)
|
|
||||||
if err != nil {
|
|
||||||
return renderers, err
|
|
||||||
}
|
|
||||||
if templFound {
|
|
||||||
renderers.LinkRenderer = hookRenderer{
|
|
||||||
templateHandler: p.s.Tmpl(),
|
|
||||||
SearchProvider: templ.(identity.SearchProvider),
|
|
||||||
templ: templ,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
layoutDescriptor.Kind = "render-image"
|
|
||||||
templ, templFound, err = p.s.Tmpl().LookupLayout(layoutDescriptor, f)
|
|
||||||
if err != nil {
|
|
||||||
return renderers, err
|
|
||||||
}
|
|
||||||
if templFound {
|
|
||||||
renderers.ImageRenderer = hookRenderer{
|
|
||||||
templateHandler: p.s.Tmpl(),
|
|
||||||
SearchProvider: templ.(identity.SearchProvider),
|
|
||||||
templ: templ,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
layoutDescriptor.Kind = "render-heading"
|
|
||||||
templ, templFound, err = p.s.Tmpl().LookupLayout(layoutDescriptor, f)
|
|
||||||
if err != nil {
|
|
||||||
return renderers, err
|
|
||||||
}
|
|
||||||
if templFound {
|
|
||||||
renderers.HeadingRenderer = hookRenderer{
|
|
||||||
templateHandler: p.s.Tmpl(),
|
|
||||||
SearchProvider: templ.(identity.SearchProvider),
|
|
||||||
templ: templ,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return renderers, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pageState) getLayoutDescriptor() output.LayoutDescriptor {
|
func (p *pageState) getLayoutDescriptor() output.LayoutDescriptor {
|
||||||
p.layoutDescriptorInit.Do(func() {
|
p.layoutDescriptorInit.Do(func() {
|
||||||
var section string
|
var section string
|
||||||
|
@ -867,7 +825,7 @@ func (p *pageState) shiftToOutputFormat(isRenderingSite bool, idx int) error {
|
||||||
|
|
||||||
if isRenderingSite {
|
if isRenderingSite {
|
||||||
cp := p.pageOutput.cp
|
cp := p.pageOutput.cp
|
||||||
if cp == nil {
|
if cp == nil && p.reusePageOutputContent() {
|
||||||
// Look for content to reuse.
|
// Look for content to reuse.
|
||||||
for i := 0; i < len(p.pageOutputs); i++ {
|
for i := 0; i < len(p.pageOutputs); i++ {
|
||||||
if i == idx {
|
if i == idx {
|
||||||
|
@ -875,7 +833,7 @@ func (p *pageState) shiftToOutputFormat(isRenderingSite bool, idx int) error {
|
||||||
}
|
}
|
||||||
po := p.pageOutputs[i]
|
po := p.pageOutputs[i]
|
||||||
|
|
||||||
if po.cp != nil && po.cp.reuse {
|
if po.cp != nil {
|
||||||
cp = po.cp
|
cp = po.cp
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@ import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"go.uber.org/atomic"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/hugo"
|
"github.com/gohugoio/hugo/common/hugo"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/maps"
|
"github.com/gohugoio/hugo/common/maps"
|
||||||
|
@ -37,6 +39,7 @@ func newPageBase(metaProvider *pageMeta) (*pageState, error) {
|
||||||
|
|
||||||
ps := &pageState{
|
ps := &pageState{
|
||||||
pageOutput: nopPageOutput,
|
pageOutput: nopPageOutput,
|
||||||
|
pageOutputTemplateVariationsState: atomic.NewUint32(0),
|
||||||
pageCommon: &pageCommon{
|
pageCommon: &pageCommon{
|
||||||
FileProvider: metaProvider,
|
FileProvider: metaProvider,
|
||||||
AuthorProvider: metaProvider,
|
AuthorProvider: metaProvider,
|
||||||
|
|
|
@ -32,6 +32,7 @@ import (
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/markup/converter"
|
"github.com/gohugoio/hugo/markup/converter"
|
||||||
|
|
||||||
|
"github.com/alecthomas/chroma/lexers"
|
||||||
"github.com/gohugoio/hugo/lazy"
|
"github.com/gohugoio/hugo/lazy"
|
||||||
|
|
||||||
bp "github.com/gohugoio/hugo/bufferpool"
|
bp "github.com/gohugoio/hugo/bufferpool"
|
||||||
|
@ -109,16 +110,8 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
enableReuse := !(hasShortcodeVariants || cp.renderHooksHaveVariants)
|
if hasShortcodeVariants {
|
||||||
|
p.pageOutputTemplateVariationsState.Store(2)
|
||||||
if enableReuse {
|
|
||||||
// Reuse this for the other output formats.
|
|
||||||
// We may improve on this, but we really want to avoid re-rendering the content
|
|
||||||
// to all output formats.
|
|
||||||
// The current rule is that if you need output format-aware shortcodes or
|
|
||||||
// content rendering hooks, create a output format-specific template, e.g.
|
|
||||||
// myshortcode.amp.html.
|
|
||||||
cp.enableReuse()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cp.workContent = p.contentToRender(cp.contentPlaceholders)
|
cp.workContent = p.contentToRender(cp.contentPlaceholders)
|
||||||
|
@ -199,19 +192,10 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recursive loops can only happen in content files with template code (shortcodes etc.)
|
// There may be recursive loops in shortcodes and render hooks.
|
||||||
// Avoid creating new goroutines if we don't have to.
|
|
||||||
needTimeout := p.shortcodeState.hasShortcodes() || cp.renderHooks != nil
|
|
||||||
|
|
||||||
if needTimeout {
|
|
||||||
cp.initMain = parent.BranchWithTimeout(p.s.siteCfg.timeout, func(ctx context.Context) (interface{}, error) {
|
cp.initMain = parent.BranchWithTimeout(p.s.siteCfg.timeout, func(ctx context.Context) (interface{}, error) {
|
||||||
return nil, initContent()
|
return nil, initContent()
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
cp.initMain = parent.Branch(func() (interface{}, error) {
|
|
||||||
return nil, initContent()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
cp.initPlain = cp.initMain.Branch(func() (interface{}, error) {
|
cp.initPlain = cp.initMain.Branch(func() (interface{}, error) {
|
||||||
cp.plain = helpers.StripHTML(string(cp.content))
|
cp.plain = helpers.StripHTML(string(cp.content))
|
||||||
|
@ -229,7 +213,7 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type renderHooks struct {
|
type renderHooks struct {
|
||||||
hooks hooks.Renderers
|
getRenderer hooks.GetRendererFunc
|
||||||
init sync.Once
|
init sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,10 +221,6 @@ type renderHooks struct {
|
||||||
type pageContentOutput struct {
|
type pageContentOutput struct {
|
||||||
f output.Format
|
f output.Format
|
||||||
|
|
||||||
// If we can reuse this for other output formats.
|
|
||||||
reuse bool
|
|
||||||
reuseInit sync.Once
|
|
||||||
|
|
||||||
p *pageState
|
p *pageState
|
||||||
|
|
||||||
// Lazy load dependencies
|
// Lazy load dependencies
|
||||||
|
@ -250,13 +230,9 @@ type pageContentOutput struct {
|
||||||
placeholdersEnabled bool
|
placeholdersEnabled bool
|
||||||
placeholdersEnabledInit sync.Once
|
placeholdersEnabledInit sync.Once
|
||||||
|
|
||||||
|
// Renders Markdown hooks.
|
||||||
renderHooks *renderHooks
|
renderHooks *renderHooks
|
||||||
|
|
||||||
// Set if there are more than one output format variant
|
|
||||||
renderHooksHaveVariants bool // TODO(bep) reimplement this in another way, consolidate with shortcodes
|
|
||||||
|
|
||||||
// Content state
|
|
||||||
|
|
||||||
workContent []byte
|
workContent []byte
|
||||||
dependencyTracker identity.Manager // Set in server mode.
|
dependencyTracker identity.Manager // Set in server mode.
|
||||||
|
|
||||||
|
@ -440,55 +416,107 @@ func (p *pageContentOutput) initRenderHooks() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var initErr error
|
|
||||||
|
|
||||||
p.renderHooks.init.Do(func() {
|
p.renderHooks.init.Do(func() {
|
||||||
ps := p.p
|
if p.p.pageOutputTemplateVariationsState.Load() == 0 {
|
||||||
|
p.p.pageOutputTemplateVariationsState.Store(1)
|
||||||
c := ps.getContentConverter()
|
|
||||||
if c == nil || !c.Supports(converter.FeatureRenderHooks) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h, err := ps.createRenderHooks(p.f)
|
type cacheKey struct {
|
||||||
|
tp hooks.RendererType
|
||||||
|
id interface{}
|
||||||
|
f output.Format
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCache := make(map[cacheKey]interface{})
|
||||||
|
var renderCacheMu sync.Mutex
|
||||||
|
|
||||||
|
p.renderHooks.getRenderer = func(tp hooks.RendererType, id interface{}) interface{} {
|
||||||
|
renderCacheMu.Lock()
|
||||||
|
defer renderCacheMu.Unlock()
|
||||||
|
|
||||||
|
key := cacheKey{tp: tp, id: id, f: p.f}
|
||||||
|
if r, ok := renderCache[key]; ok {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
layoutDescriptor := p.p.getLayoutDescriptor()
|
||||||
|
layoutDescriptor.RenderingHook = true
|
||||||
|
layoutDescriptor.LayoutOverride = false
|
||||||
|
layoutDescriptor.Layout = ""
|
||||||
|
|
||||||
|
switch tp {
|
||||||
|
case hooks.LinkRendererType:
|
||||||
|
layoutDescriptor.Kind = "render-link"
|
||||||
|
case hooks.ImageRendererType:
|
||||||
|
layoutDescriptor.Kind = "render-image"
|
||||||
|
case hooks.HeadingRendererType:
|
||||||
|
layoutDescriptor.Kind = "render-heading"
|
||||||
|
case hooks.CodeBlockRendererType:
|
||||||
|
layoutDescriptor.Kind = "render-codeblock"
|
||||||
|
if id != nil {
|
||||||
|
lang := id.(string)
|
||||||
|
lexer := lexers.Get(lang)
|
||||||
|
if lexer != nil {
|
||||||
|
layoutDescriptor.KindVariants = strings.Join(lexer.Config().Aliases, ",")
|
||||||
|
} else {
|
||||||
|
layoutDescriptor.KindVariants = lang
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getHookTemplate := func(f output.Format) (tpl.Template, bool) {
|
||||||
|
templ, found, err := p.p.s.Tmpl().LookupLayout(layoutDescriptor, f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
initErr = err
|
panic(err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
p.renderHooks.hooks = h
|
return templ, found
|
||||||
|
|
||||||
if !p.renderHooksHaveVariants || h.IsZero() {
|
|
||||||
// Check if there is a different render hooks template
|
|
||||||
// for any of the other page output formats.
|
|
||||||
// If not, we can reuse this.
|
|
||||||
for _, po := range ps.pageOutputs {
|
|
||||||
if po.f.Name != p.f.Name {
|
|
||||||
h2, err := ps.createRenderHooks(po.f)
|
|
||||||
if err != nil {
|
|
||||||
initErr = err
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if h2.IsZero() {
|
templ, found1 := getHookTemplate(p.f)
|
||||||
|
|
||||||
|
if p.p.reusePageOutputContent() {
|
||||||
|
// Check if some of the other output formats would give a different template.
|
||||||
|
for _, f := range p.p.s.renderFormats {
|
||||||
|
if f.Name == p.f.Name {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
templ2, found2 := getHookTemplate(f)
|
||||||
if p.renderHooks.hooks.IsZero() {
|
if found2 {
|
||||||
p.renderHooks.hooks = h2
|
if !found1 {
|
||||||
}
|
templ = templ2
|
||||||
|
found1 = true
|
||||||
p.renderHooksHaveVariants = !h2.Eq(p.renderHooks.hooks)
|
|
||||||
|
|
||||||
if p.renderHooksHaveVariants {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if templ != templ2 {
|
||||||
|
p.p.pageOutputTemplateVariationsState.Store(2)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found1 {
|
||||||
|
if tp == hooks.CodeBlockRendererType {
|
||||||
|
// No user provided tempplate for code blocks, so we use the native Go code version -- which is also faster.
|
||||||
|
r := p.p.s.ContentSpec.Converters.GetHighlighter()
|
||||||
|
renderCache[key] = r
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r := hookRendererTemplate{
|
||||||
|
templateHandler: p.p.s.Tmpl(),
|
||||||
|
SearchProvider: templ.(identity.SearchProvider),
|
||||||
|
templ: templ,
|
||||||
|
}
|
||||||
|
renderCache[key] = r
|
||||||
|
return r
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return initErr
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pageContentOutput) setAutoSummary() error {
|
func (p *pageContentOutput) setAutoSummary() error {
|
||||||
|
@ -512,6 +540,9 @@ func (p *pageContentOutput) setAutoSummary() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *pageContentOutput) renderContent(content []byte, renderTOC bool) (converter.Result, error) {
|
func (cp *pageContentOutput) renderContent(content []byte, renderTOC bool) (converter.Result, error) {
|
||||||
|
if err := cp.initRenderHooks(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
c := cp.p.getContentConverter()
|
c := cp.p.getContentConverter()
|
||||||
return cp.renderContentWithConverter(c, content, renderTOC)
|
return cp.renderContentWithConverter(c, content, renderTOC)
|
||||||
}
|
}
|
||||||
|
@ -521,7 +552,7 @@ func (cp *pageContentOutput) renderContentWithConverter(c converter.Converter, c
|
||||||
converter.RenderContext{
|
converter.RenderContext{
|
||||||
Src: content,
|
Src: content,
|
||||||
RenderTOC: renderTOC,
|
RenderTOC: renderTOC,
|
||||||
RenderHooks: cp.renderHooks.hooks,
|
GetRenderer: cp.renderHooks.getRenderer,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -570,12 +601,6 @@ func (p *pageContentOutput) enablePlaceholders() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pageContentOutput) enableReuse() {
|
|
||||||
p.reuseInit.Do(func() {
|
|
||||||
p.reuse = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// these will be shifted out when rendering a given output format.
|
// these will be shifted out when rendering a given output format.
|
||||||
type pagePerOutputProviders interface {
|
type pagePerOutputProviders interface {
|
||||||
targetPather
|
targetPather
|
||||||
|
|
|
@ -428,8 +428,7 @@ func testAllMarkdownEnginesForPages(t *testing.T,
|
||||||
|
|
||||||
assertFunc(t, e.ext, s.RegularPages())
|
assertFunc(t, e.ext, s.RegularPages())
|
||||||
|
|
||||||
home, err := s.Info.Home()
|
home := s.Info.Home()
|
||||||
b.Assert(err, qt.IsNil)
|
|
||||||
b.Assert(home, qt.Not(qt.IsNil))
|
b.Assert(home, qt.Not(qt.IsNil))
|
||||||
b.Assert(home.File().Path(), qt.Equals, homePath)
|
b.Assert(home.File().Path(), qt.Equals, homePath)
|
||||||
b.Assert(content(home), qt.Contains, "Home Page Content")
|
b.Assert(content(home), qt.Contains, "Home Page Content")
|
||||||
|
@ -1286,7 +1285,7 @@ func TestTranslationKey(t *testing.T) {
|
||||||
|
|
||||||
c.Assert(len(s.RegularPages()), qt.Equals, 2)
|
c.Assert(len(s.RegularPages()), qt.Equals, 2)
|
||||||
|
|
||||||
home, _ := s.Info.Home()
|
home := s.Info.Home()
|
||||||
c.Assert(home, qt.Not(qt.IsNil))
|
c.Assert(home, qt.Not(qt.IsNil))
|
||||||
c.Assert(home.TranslationKey(), qt.Equals, "home")
|
c.Assert(home.TranslationKey(), qt.Equals, "home")
|
||||||
c.Assert(s.RegularPages()[0].TranslationKey(), qt.Equals, "page/k1")
|
c.Assert(s.RegularPages()[0].TranslationKey(), qt.Equals, "page/k1")
|
||||||
|
|
|
@ -150,7 +150,7 @@ func TestPageBundlerSiteRegular(t *testing.T) {
|
||||||
c.Assert(leafBundle1.Section(), qt.Equals, "b")
|
c.Assert(leafBundle1.Section(), qt.Equals, "b")
|
||||||
sectionB := s.getPage(page.KindSection, "b")
|
sectionB := s.getPage(page.KindSection, "b")
|
||||||
c.Assert(sectionB, qt.Not(qt.IsNil))
|
c.Assert(sectionB, qt.Not(qt.IsNil))
|
||||||
home, _ := s.Info.Home()
|
home := s.Info.Home()
|
||||||
c.Assert(home.BundleType(), qt.Equals, files.ContentClassBranch)
|
c.Assert(home.BundleType(), qt.Equals, files.ContentClassBranch)
|
||||||
|
|
||||||
// This is a root bundle and should live in the "home section"
|
// This is a root bundle and should live in the "home section"
|
||||||
|
|
|
@ -30,6 +30,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/hugio"
|
||||||
"github.com/gohugoio/hugo/common/types"
|
"github.com/gohugoio/hugo/common/types"
|
||||||
"github.com/gohugoio/hugo/modules"
|
"github.com/gohugoio/hugo/modules"
|
||||||
"golang.org/x/text/unicode/norm"
|
"golang.org/x/text/unicode/norm"
|
||||||
|
@ -54,12 +55,11 @@ import (
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/maps"
|
"github.com/gohugoio/hugo/common/maps"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/text"
|
"github.com/gohugoio/hugo/common/text"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/hugo"
|
"github.com/gohugoio/hugo/common/hugo"
|
||||||
"github.com/gohugoio/hugo/publisher"
|
"github.com/gohugoio/hugo/publisher"
|
||||||
|
"github.com/pkg/errors"
|
||||||
_errors "github.com/pkg/errors"
|
_errors "github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/langs"
|
"github.com/gohugoio/hugo/langs"
|
||||||
|
@ -1773,19 +1773,23 @@ var infoOnMissingLayout = map[string]bool{
|
||||||
"404": true,
|
"404": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// hookRenderer is the canonical implementation of all hooks.ITEMRenderer,
|
// hookRendererTemplate is the canonical implementation of all hooks.ITEMRenderer,
|
||||||
// where ITEM is the thing being hooked.
|
// where ITEM is the thing being hooked.
|
||||||
type hookRenderer struct {
|
type hookRendererTemplate struct {
|
||||||
templateHandler tpl.TemplateHandler
|
templateHandler tpl.TemplateHandler
|
||||||
identity.SearchProvider
|
identity.SearchProvider
|
||||||
templ tpl.Template
|
templ tpl.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hr hookRenderer) RenderLink(w io.Writer, ctx hooks.LinkContext) error {
|
func (hr hookRendererTemplate) RenderLink(w io.Writer, ctx hooks.LinkContext) error {
|
||||||
return hr.templateHandler.Execute(hr.templ, w, ctx)
|
return hr.templateHandler.Execute(hr.templ, w, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hr hookRenderer) RenderHeading(w io.Writer, ctx hooks.HeadingContext) error {
|
func (hr hookRendererTemplate) RenderHeading(w io.Writer, ctx hooks.HeadingContext) error {
|
||||||
|
return hr.templateHandler.Execute(hr.templ, w, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hr hookRendererTemplate) RenderCodeblock(w hugio.FlexiWriter, ctx hooks.CodeblockContext) error {
|
||||||
return hr.templateHandler.Execute(hr.templ, w, ctx)
|
return hr.templateHandler.Execute(hr.templ, w, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,14 +19,10 @@ import (
|
||||||
|
|
||||||
// Sections returns the top level sections.
|
// Sections returns the top level sections.
|
||||||
func (s *SiteInfo) Sections() page.Pages {
|
func (s *SiteInfo) Sections() page.Pages {
|
||||||
home, err := s.Home()
|
return s.Home().Sections()
|
||||||
if err == nil {
|
|
||||||
return home.Sections()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Home is a shortcut to the home page, equivalent to .Site.GetPage "home".
|
// Home is a shortcut to the home page, equivalent to .Site.GetPage "home".
|
||||||
func (s *SiteInfo) Home() (page.Page, error) {
|
func (s *SiteInfo) Home() page.Page {
|
||||||
return s.s.home, nil
|
return s.s.home
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/gohugoio/hugo/config"
|
"github.com/gohugoio/hugo/config"
|
||||||
"github.com/gohugoio/hugo/identity"
|
"github.com/gohugoio/hugo/identity"
|
||||||
"github.com/gohugoio/hugo/markup/converter/hooks"
|
"github.com/gohugoio/hugo/markup/converter/hooks"
|
||||||
|
"github.com/gohugoio/hugo/markup/highlight"
|
||||||
"github.com/gohugoio/hugo/markup/markup_config"
|
"github.com/gohugoio/hugo/markup/markup_config"
|
||||||
"github.com/gohugoio/hugo/markup/tableofcontents"
|
"github.com/gohugoio/hugo/markup/tableofcontents"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
|
@ -34,7 +35,7 @@ type ProviderConfig struct {
|
||||||
ContentFs afero.Fs
|
ContentFs afero.Fs
|
||||||
Logger loggers.Logger
|
Logger loggers.Logger
|
||||||
Exec *hexec.Exec
|
Exec *hexec.Exec
|
||||||
Highlight func(code, lang, optsStr string) (string, error)
|
highlight.Highlighter
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProviderProvider creates converter providers.
|
// ProviderProvider creates converter providers.
|
||||||
|
@ -129,7 +130,8 @@ type DocumentContext struct {
|
||||||
type RenderContext struct {
|
type RenderContext struct {
|
||||||
Src []byte
|
Src []byte
|
||||||
RenderTOC bool
|
RenderTOC bool
|
||||||
RenderHooks hooks.Renderers
|
|
||||||
|
GetRenderer hooks.GetRendererFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
var FeatureRenderHooks = identity.NewPathIdentity("markup", "renderingHooks")
|
var FeatureRenderHooks = identity.NewPathIdentity("markup", "renderingHooks")
|
||||||
|
|
|
@ -14,15 +14,17 @@
|
||||||
package hooks
|
package hooks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/hugio"
|
||||||
"github.com/gohugoio/hugo/identity"
|
"github.com/gohugoio/hugo/identity"
|
||||||
|
"github.com/gohugoio/hugo/markup/internal/attributes"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ AttributesOptionsSliceProvider = (*attributes.AttributesHolder)(nil)
|
||||||
|
|
||||||
type AttributesProvider interface {
|
type AttributesProvider interface {
|
||||||
Attributes() map[string]string
|
Attributes() map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type LinkContext interface {
|
type LinkContext interface {
|
||||||
|
@ -33,11 +35,30 @@ type LinkContext interface {
|
||||||
PlainText() string
|
PlainText() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CodeblockContext interface {
|
||||||
|
AttributesProvider
|
||||||
|
Options() map[string]interface{}
|
||||||
|
Lang() string
|
||||||
|
Code() string
|
||||||
|
Ordinal() int
|
||||||
|
Page() interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type AttributesOptionsSliceProvider interface {
|
||||||
|
AttributesSlice() []attributes.Attribute
|
||||||
|
OptionsSlice() []attributes.Attribute
|
||||||
|
}
|
||||||
|
|
||||||
type LinkRenderer interface {
|
type LinkRenderer interface {
|
||||||
RenderLink(w io.Writer, ctx LinkContext) error
|
RenderLink(w io.Writer, ctx LinkContext) error
|
||||||
identity.Provider
|
identity.Provider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CodeBlockRenderer interface {
|
||||||
|
RenderCodeblock(w hugio.FlexiWriter, ctx CodeblockContext) error
|
||||||
|
identity.Provider
|
||||||
|
}
|
||||||
|
|
||||||
// HeadingContext contains accessors to all attributes that a HeadingRenderer
|
// HeadingContext contains accessors to all attributes that a HeadingRenderer
|
||||||
// can use to render a heading.
|
// can use to render a heading.
|
||||||
type HeadingContext interface {
|
type HeadingContext interface {
|
||||||
|
@ -63,70 +84,13 @@ type HeadingRenderer interface {
|
||||||
identity.Provider
|
identity.Provider
|
||||||
}
|
}
|
||||||
|
|
||||||
type Renderers struct {
|
type RendererType int
|
||||||
LinkRenderer LinkRenderer
|
|
||||||
ImageRenderer LinkRenderer
|
|
||||||
HeadingRenderer HeadingRenderer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r Renderers) Eq(other interface{}) bool {
|
const (
|
||||||
ro, ok := other.(Renderers)
|
LinkRendererType RendererType = iota + 1
|
||||||
if !ok {
|
ImageRendererType
|
||||||
return false
|
HeadingRendererType
|
||||||
}
|
CodeBlockRendererType
|
||||||
|
)
|
||||||
|
|
||||||
if r.IsZero() || ro.IsZero() {
|
type GetRendererFunc func(t RendererType, id interface{}) interface{}
|
||||||
return r.IsZero() && ro.IsZero()
|
|
||||||
}
|
|
||||||
|
|
||||||
var b1, b2 bool
|
|
||||||
b1, b2 = r.ImageRenderer == nil, ro.ImageRenderer == nil
|
|
||||||
if (b1 || b2) && (b1 != b2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !b1 && r.ImageRenderer.GetIdentity() != ro.ImageRenderer.GetIdentity() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
b1, b2 = r.LinkRenderer == nil, ro.LinkRenderer == nil
|
|
||||||
if (b1 || b2) && (b1 != b2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !b1 && r.LinkRenderer.GetIdentity() != ro.LinkRenderer.GetIdentity() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
b1, b2 = r.HeadingRenderer == nil, ro.HeadingRenderer == nil
|
|
||||||
if (b1 || b2) && (b1 != b2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !b1 && r.HeadingRenderer.GetIdentity() != ro.HeadingRenderer.GetIdentity() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r Renderers) IsZero() bool {
|
|
||||||
return r.HeadingRenderer == nil && r.LinkRenderer == nil && r.ImageRenderer == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r Renderers) String() string {
|
|
||||||
if r.IsZero() {
|
|
||||||
return "<zero>"
|
|
||||||
}
|
|
||||||
|
|
||||||
var sb strings.Builder
|
|
||||||
|
|
||||||
if r.LinkRenderer != nil {
|
|
||||||
sb.WriteString(fmt.Sprintf("LinkRenderer<%s>|", r.LinkRenderer.GetIdentity()))
|
|
||||||
}
|
|
||||||
if r.HeadingRenderer != nil {
|
|
||||||
sb.WriteString(fmt.Sprintf("HeadingRenderer<%s>|", r.HeadingRenderer.GetIdentity()))
|
|
||||||
}
|
|
||||||
if r.ImageRenderer != nil {
|
|
||||||
sb.WriteString(fmt.Sprintf("ImageRenderer<%s>|", r.ImageRenderer.GetIdentity()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.String()
|
|
||||||
}
|
|
||||||
|
|
115
markup/goldmark/codeblocks/integration_test.go
Normal file
115
markup/goldmark/codeblocks/integration_test.go
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
// 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 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 codeblocks_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/hugolib"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCodeblocks(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
files := `
|
||||||
|
-- config.toml --
|
||||||
|
[markup]
|
||||||
|
[markup.highlight]
|
||||||
|
anchorLineNos = false
|
||||||
|
codeFences = true
|
||||||
|
guessSyntax = false
|
||||||
|
hl_Lines = ''
|
||||||
|
lineAnchors = ''
|
||||||
|
lineNoStart = 1
|
||||||
|
lineNos = false
|
||||||
|
lineNumbersInTable = true
|
||||||
|
noClasses = false
|
||||||
|
style = 'monokai'
|
||||||
|
tabWidth = 4
|
||||||
|
-- layouts/_default/_markup/render-codeblock-goat.html --
|
||||||
|
{{ $diagram := diagrams.Goat .Code }}
|
||||||
|
Goat SVG:{{ substr $diagram.SVG 0 100 | safeHTML }} }}|
|
||||||
|
Goat Attribute: {{ .Attributes.width}}|
|
||||||
|
-- layouts/_default/_markup/render-codeblock-go.html --
|
||||||
|
Go Code: {{ .Code | safeHTML }}|
|
||||||
|
Go Language: {{ .Lang }}|
|
||||||
|
-- layouts/_default/single.html --
|
||||||
|
{{ .Content }}
|
||||||
|
-- content/p1.md --
|
||||||
|
---
|
||||||
|
title: "p1"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ascii Diagram
|
||||||
|
|
||||||
|
CODE_FENCEgoat { width="600" }
|
||||||
|
--->
|
||||||
|
CODE_FENCE
|
||||||
|
|
||||||
|
## Go Code
|
||||||
|
|
||||||
|
CODE_FENCEgo
|
||||||
|
fmt.Println("Hello, World!");
|
||||||
|
CODE_FENCE
|
||||||
|
|
||||||
|
## Golang Code
|
||||||
|
|
||||||
|
CODE_FENCEgolang
|
||||||
|
fmt.Println("Hello, Golang!");
|
||||||
|
CODE_FENCE
|
||||||
|
|
||||||
|
## Bash Code
|
||||||
|
|
||||||
|
CODE_FENCEbash { linenos=inline,hl_lines=[2,"5-6"],linenostart=32 class=blue }
|
||||||
|
echo "l1";
|
||||||
|
echo "l2";
|
||||||
|
echo "l3";
|
||||||
|
echo "l4";
|
||||||
|
echo "l5";
|
||||||
|
echo "l6";
|
||||||
|
echo "l7";
|
||||||
|
echo "l8";
|
||||||
|
CODE_FENCE
|
||||||
|
`
|
||||||
|
|
||||||
|
files = strings.ReplaceAll(files, "CODE_FENCE", "```")
|
||||||
|
|
||||||
|
b := hugolib.NewIntegrationTestBuilder(
|
||||||
|
hugolib.IntegrationTestConfig{
|
||||||
|
T: t,
|
||||||
|
TxtarString: files,
|
||||||
|
NeedsOsFS: false,
|
||||||
|
},
|
||||||
|
).Build()
|
||||||
|
|
||||||
|
b.AssertFileContent("public/p1/index.html", `
|
||||||
|
Goat SVG:<svg class='diagram'
|
||||||
|
Goat Attribute: 600|
|
||||||
|
|
||||||
|
Go Language: go|
|
||||||
|
Go Code: fmt.Println("Hello, World!");
|
||||||
|
|
||||||
|
Go Code: fmt.Println("Hello, Golang!");
|
||||||
|
Go Language: golang|
|
||||||
|
|
||||||
|
|
||||||
|
`,
|
||||||
|
"Goat SVG:<svg class='diagram' xmlns='http://www.w3.org/2000/svg' version='1.1' height='25' width='40'",
|
||||||
|
"Goat Attribute: 600|",
|
||||||
|
"<h2 id=\"go-code\">Go Code</h2>\nGo Code: fmt.Println(\"Hello, World!\");\n|\nGo Language: go|",
|
||||||
|
"<h2 id=\"golang-code\">Golang Code</h2>\nGo Code: fmt.Println(\"Hello, Golang!\");\n|\nGo Language: golang|",
|
||||||
|
"<h2 id=\"bash-code\">Bash Code</h2>\n<div class=\"highlight blue\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"line\"><span class=\"ln\">32</span><span class=\"cl\"><span class=\"nb\">echo</span> <span class=\"s2\">"l1"</span><span class=\"p\">;</span>\n</span></span><span class=\"line hl\"><span class=\"ln\">33</span>",
|
||||||
|
)
|
||||||
|
}
|
159
markup/goldmark/codeblocks/render.go
Normal file
159
markup/goldmark/codeblocks/render.go
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
// 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 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 codeblocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/markup/converter/hooks"
|
||||||
|
"github.com/gohugoio/hugo/markup/goldmark/internal/render"
|
||||||
|
"github.com/gohugoio/hugo/markup/internal/attributes"
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
"github.com/yuin/goldmark/ast"
|
||||||
|
"github.com/yuin/goldmark/parser"
|
||||||
|
"github.com/yuin/goldmark/renderer"
|
||||||
|
"github.com/yuin/goldmark/text"
|
||||||
|
"github.com/yuin/goldmark/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
diagrams struct{}
|
||||||
|
htmlRenderer struct{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func New() goldmark.Extender {
|
||||||
|
return &diagrams{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *diagrams) Extend(m goldmark.Markdown) {
|
||||||
|
m.Parser().AddOptions(
|
||||||
|
parser.WithASTTransformers(
|
||||||
|
util.Prioritized(&Transformer{}, 100),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
||||||
|
util.Prioritized(newHTMLRenderer(), 100),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHTMLRenderer() renderer.NodeRenderer {
|
||||||
|
r := &htmlRenderer{}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *htmlRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||||
|
reg.Register(KindCodeBlock, r.renderCodeBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
|
ctx := w.(*render.Context)
|
||||||
|
|
||||||
|
if entering {
|
||||||
|
return ast.WalkContinue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n := node.(*codeBlock)
|
||||||
|
lang := string(n.b.Language(src))
|
||||||
|
ordinal := n.ordinal
|
||||||
|
|
||||||
|
var buff bytes.Buffer
|
||||||
|
|
||||||
|
l := n.b.Lines().Len()
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
line := n.b.Lines().At(i)
|
||||||
|
buff.Write(line.Value(src))
|
||||||
|
}
|
||||||
|
text := buff.String()
|
||||||
|
|
||||||
|
var info []byte
|
||||||
|
if n.b.Info != nil {
|
||||||
|
info = n.b.Info.Segment.Value(src)
|
||||||
|
}
|
||||||
|
attrs := getAttributes(n.b, info)
|
||||||
|
|
||||||
|
v := ctx.RenderContext().GetRenderer(hooks.CodeBlockRendererType, lang)
|
||||||
|
if v == nil {
|
||||||
|
return ast.WalkStop, fmt.Errorf("no code renderer found for %q", lang)
|
||||||
|
}
|
||||||
|
|
||||||
|
cr := v.(hooks.CodeBlockRenderer)
|
||||||
|
|
||||||
|
err := cr.RenderCodeblock(
|
||||||
|
w,
|
||||||
|
codeBlockContext{
|
||||||
|
page: ctx.DocumentContext().Document,
|
||||||
|
lang: lang,
|
||||||
|
code: text,
|
||||||
|
ordinal: ordinal,
|
||||||
|
AttributesHolder: attributes.New(attrs, attributes.AttributesOwnerCodeBlock),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx.AddIdentity(cr)
|
||||||
|
|
||||||
|
return ast.WalkContinue, err
|
||||||
|
}
|
||||||
|
|
||||||
|
type codeBlockContext struct {
|
||||||
|
page interface{}
|
||||||
|
lang string
|
||||||
|
code string
|
||||||
|
ordinal int
|
||||||
|
*attributes.AttributesHolder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c codeBlockContext) Page() interface{} {
|
||||||
|
return c.page
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c codeBlockContext) Lang() string {
|
||||||
|
return c.lang
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c codeBlockContext) Code() string {
|
||||||
|
return c.code
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c codeBlockContext) Ordinal() int {
|
||||||
|
return c.ordinal
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAttributes(node *ast.FencedCodeBlock, infostr []byte) []ast.Attribute {
|
||||||
|
if node.Attributes() != nil {
|
||||||
|
return node.Attributes()
|
||||||
|
}
|
||||||
|
if infostr != nil {
|
||||||
|
attrStartIdx := -1
|
||||||
|
|
||||||
|
for idx, char := range infostr {
|
||||||
|
if char == '{' {
|
||||||
|
attrStartIdx = idx
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if attrStartIdx > 0 {
|
||||||
|
n := ast.NewTextBlock() // dummy node for storing attributes
|
||||||
|
attrStr := infostr[attrStartIdx:]
|
||||||
|
if attrs, hasAttr := parser.ParseAttributes(text.NewReader(attrStr)); hasAttr {
|
||||||
|
for _, attr := range attrs {
|
||||||
|
n.SetAttribute(attr.Name, attr.Value)
|
||||||
|
}
|
||||||
|
return n.Attributes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
53
markup/goldmark/codeblocks/transform.go
Normal file
53
markup/goldmark/codeblocks/transform.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package codeblocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/yuin/goldmark/ast"
|
||||||
|
"github.com/yuin/goldmark/parser"
|
||||||
|
"github.com/yuin/goldmark/text"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Kind is the kind of an Hugo code block.
|
||||||
|
var KindCodeBlock = ast.NewNodeKind("HugoCodeBlock")
|
||||||
|
|
||||||
|
// Its raw contents are the plain text of the code block.
|
||||||
|
type codeBlock struct {
|
||||||
|
ast.BaseBlock
|
||||||
|
ordinal int
|
||||||
|
b *ast.FencedCodeBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*codeBlock) Kind() ast.NodeKind { return KindCodeBlock }
|
||||||
|
|
||||||
|
func (*codeBlock) IsRaw() bool { return true }
|
||||||
|
|
||||||
|
func (b *codeBlock) Dump(src []byte, level int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
type Transformer struct{}
|
||||||
|
|
||||||
|
// Transform transforms the provided Markdown AST.
|
||||||
|
func (*Transformer) Transform(doc *ast.Document, reader text.Reader, pctx parser.Context) {
|
||||||
|
var codeBlocks []*ast.FencedCodeBlock
|
||||||
|
|
||||||
|
ast.Walk(doc, func(node ast.Node, enter bool) (ast.WalkStatus, error) {
|
||||||
|
if !enter {
|
||||||
|
return ast.WalkContinue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cb, ok := node.(*ast.FencedCodeBlock)
|
||||||
|
if !ok {
|
||||||
|
return ast.WalkContinue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
codeBlocks = append(codeBlocks, cb)
|
||||||
|
return ast.WalkContinue, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
for i, cb := range codeBlocks {
|
||||||
|
b := &codeBlock{b: cb, ordinal: i}
|
||||||
|
parent := cb.Parent()
|
||||||
|
if parent != nil {
|
||||||
|
parent.ReplaceChild(parent, cb, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,12 +17,12 @@ package goldmark
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/bits"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/markup/goldmark/codeblocks"
|
||||||
"github.com/gohugoio/hugo/markup/goldmark/internal/extensions/attributes"
|
"github.com/gohugoio/hugo/markup/goldmark/internal/extensions/attributes"
|
||||||
"github.com/yuin/goldmark/ast"
|
"github.com/gohugoio/hugo/markup/goldmark/internal/render"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/identity"
|
"github.com/gohugoio/hugo/identity"
|
||||||
|
|
||||||
|
@ -32,16 +32,13 @@ import (
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/hugofs"
|
"github.com/gohugoio/hugo/hugofs"
|
||||||
"github.com/gohugoio/hugo/markup/converter"
|
"github.com/gohugoio/hugo/markup/converter"
|
||||||
"github.com/gohugoio/hugo/markup/highlight"
|
|
||||||
"github.com/gohugoio/hugo/markup/tableofcontents"
|
"github.com/gohugoio/hugo/markup/tableofcontents"
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
hl "github.com/yuin/goldmark-highlighting"
|
|
||||||
"github.com/yuin/goldmark/extension"
|
"github.com/yuin/goldmark/extension"
|
||||||
"github.com/yuin/goldmark/parser"
|
"github.com/yuin/goldmark/parser"
|
||||||
"github.com/yuin/goldmark/renderer"
|
"github.com/yuin/goldmark/renderer"
|
||||||
"github.com/yuin/goldmark/renderer/html"
|
"github.com/yuin/goldmark/renderer/html"
|
||||||
"github.com/yuin/goldmark/text"
|
"github.com/yuin/goldmark/text"
|
||||||
"github.com/yuin/goldmark/util"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Provider is the package entry point.
|
// Provider is the package entry point.
|
||||||
|
@ -104,7 +101,7 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
|
||||||
)
|
)
|
||||||
|
|
||||||
if mcfg.Highlight.CodeFences {
|
if mcfg.Highlight.CodeFences {
|
||||||
extensions = append(extensions, newHighlighting(mcfg.Highlight))
|
extensions = append(extensions, codeblocks.New())
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Extensions.Table {
|
if cfg.Extensions.Table {
|
||||||
|
@ -178,65 +175,6 @@ func (c converterResult) GetIdentities() identity.Identities {
|
||||||
return c.ids
|
return c.ids
|
||||||
}
|
}
|
||||||
|
|
||||||
type bufWriter struct {
|
|
||||||
*bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxInt = 1<<(bits.UintSize-1) - 1
|
|
||||||
|
|
||||||
func (b *bufWriter) Available() int {
|
|
||||||
return maxInt
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bufWriter) Buffered() int {
|
|
||||||
return b.Len()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *bufWriter) Flush() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type renderContext struct {
|
|
||||||
*bufWriter
|
|
||||||
positions []int
|
|
||||||
renderContextData
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *renderContext) pushPos(n int) {
|
|
||||||
ctx.positions = append(ctx.positions, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *renderContext) popPos() int {
|
|
||||||
i := len(ctx.positions) - 1
|
|
||||||
p := ctx.positions[i]
|
|
||||||
ctx.positions = ctx.positions[:i]
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
type renderContextData interface {
|
|
||||||
RenderContext() converter.RenderContext
|
|
||||||
DocumentContext() converter.DocumentContext
|
|
||||||
AddIdentity(id identity.Provider)
|
|
||||||
}
|
|
||||||
|
|
||||||
type renderContextDataHolder struct {
|
|
||||||
rctx converter.RenderContext
|
|
||||||
dctx converter.DocumentContext
|
|
||||||
ids identity.Manager
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *renderContextDataHolder) RenderContext() converter.RenderContext {
|
|
||||||
return ctx.rctx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *renderContextDataHolder) DocumentContext() converter.DocumentContext {
|
|
||||||
return ctx.dctx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ctx *renderContextDataHolder) AddIdentity(id identity.Provider) {
|
|
||||||
ctx.ids.Add(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
var converterIdentity = identity.KeyValueIdentity{Key: "goldmark", Value: "converter"}
|
var converterIdentity = identity.KeyValueIdentity{Key: "goldmark", Value: "converter"}
|
||||||
|
|
||||||
func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result converter.Result, err error) {
|
func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result converter.Result, err error) {
|
||||||
|
@ -251,7 +189,7 @@ func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result convert
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
buf := &bufWriter{Buffer: &bytes.Buffer{}}
|
buf := &render.BufWriter{Buffer: &bytes.Buffer{}}
|
||||||
result = buf
|
result = buf
|
||||||
pctx := c.newParserContext(ctx)
|
pctx := c.newParserContext(ctx)
|
||||||
reader := text.NewReader(ctx.Src)
|
reader := text.NewReader(ctx.Src)
|
||||||
|
@ -261,15 +199,15 @@ func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result convert
|
||||||
parser.WithContext(pctx),
|
parser.WithContext(pctx),
|
||||||
)
|
)
|
||||||
|
|
||||||
rcx := &renderContextDataHolder{
|
rcx := &render.RenderContextDataHolder{
|
||||||
rctx: ctx,
|
Rctx: ctx,
|
||||||
dctx: c.ctx,
|
Dctx: c.ctx,
|
||||||
ids: identity.NewManager(converterIdentity),
|
IDs: identity.NewManager(converterIdentity),
|
||||||
}
|
}
|
||||||
|
|
||||||
w := &renderContext{
|
w := &render.Context{
|
||||||
bufWriter: buf,
|
BufWriter: buf,
|
||||||
renderContextData: rcx,
|
ContextData: rcx,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.md.Renderer().Render(w, ctx.Src, doc); err != nil {
|
if err := c.md.Renderer().Render(w, ctx.Src, doc); err != nil {
|
||||||
|
@ -278,7 +216,7 @@ func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result convert
|
||||||
|
|
||||||
return converterResult{
|
return converterResult{
|
||||||
Result: buf,
|
Result: buf,
|
||||||
ids: rcx.ids.GetIdentities(),
|
ids: rcx.IDs.GetIdentities(),
|
||||||
toc: pctx.TableOfContents(),
|
toc: pctx.TableOfContents(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -309,63 +247,3 @@ func (p *parserContext) TableOfContents() tableofcontents.Root {
|
||||||
}
|
}
|
||||||
return tableofcontents.Root{}
|
return tableofcontents.Root{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHighlighting(cfg highlight.Config) goldmark.Extender {
|
|
||||||
return hl.NewHighlighting(
|
|
||||||
hl.WithStyle(cfg.Style),
|
|
||||||
hl.WithGuessLanguage(cfg.GuessSyntax),
|
|
||||||
hl.WithCodeBlockOptions(highlight.GetCodeBlockOptions()),
|
|
||||||
hl.WithFormatOptions(
|
|
||||||
cfg.ToHTMLOptions()...,
|
|
||||||
),
|
|
||||||
|
|
||||||
hl.WithWrapperRenderer(func(w util.BufWriter, ctx hl.CodeBlockContext, entering bool) {
|
|
||||||
var language string
|
|
||||||
if l, hasLang := ctx.Language(); hasLang {
|
|
||||||
language = string(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.Highlighted() {
|
|
||||||
if entering {
|
|
||||||
writeDivStart(w, ctx)
|
|
||||||
} else {
|
|
||||||
writeDivEnd(w)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if entering {
|
|
||||||
highlight.WritePreStart(w, language, "")
|
|
||||||
} else {
|
|
||||||
highlight.WritePreEnd(w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeDivStart(w util.BufWriter, ctx hl.CodeBlockContext) {
|
|
||||||
w.WriteString(`<div class="highlight`)
|
|
||||||
|
|
||||||
var attributes []ast.Attribute
|
|
||||||
if ctx.Attributes() != nil {
|
|
||||||
attributes = ctx.Attributes().All()
|
|
||||||
}
|
|
||||||
|
|
||||||
if attributes != nil {
|
|
||||||
class, found := ctx.Attributes().GetString("class")
|
|
||||||
if found {
|
|
||||||
w.WriteString(" ")
|
|
||||||
w.Write(util.EscapeHTML(class.([]byte)))
|
|
||||||
|
|
||||||
}
|
|
||||||
_, _ = w.WriteString("\"")
|
|
||||||
renderAttributes(w, true, attributes...)
|
|
||||||
} else {
|
|
||||||
_, _ = w.WriteString("\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteString(">")
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeDivEnd(w util.BufWriter) {
|
|
||||||
w.WriteString("</div>")
|
|
||||||
}
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
|
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/markup/converter/hooks"
|
||||||
"github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
|
"github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/markup/highlight"
|
"github.com/gohugoio/hugo/markup/highlight"
|
||||||
|
@ -41,9 +42,18 @@ func convert(c *qt.C, mconf markup_config.Config, content string) converter.Resu
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
|
h := highlight.New(mconf.Highlight)
|
||||||
|
|
||||||
|
getRenderer := func(t hooks.RendererType, id interface{}) interface{} {
|
||||||
|
if t == hooks.CodeBlockRendererType {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
conv, err := p.New(converter.DocumentContext{DocumentID: "thedoc"})
|
conv, err := p.New(converter.DocumentContext{DocumentID: "thedoc"})
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
b, err := conv.Convert(converter.RenderContext{RenderTOC: true, Src: []byte(content)})
|
b, err := conv.Convert(converter.RenderContext{RenderTOC: true, Src: []byte(content), GetRenderer: getRenderer})
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
|
|
||||||
return b
|
return b
|
||||||
|
@ -372,12 +382,21 @@ LINE5
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
h := highlight.New(conf)
|
||||||
|
|
||||||
|
getRenderer := func(t hooks.RendererType, id interface{}) interface{} {
|
||||||
|
if t == hooks.CodeBlockRendererType {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
content := "```" + language + "\n" + code + "\n```"
|
content := "```" + language + "\n" + code + "\n```"
|
||||||
|
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
conv, err := p.New(converter.DocumentContext{})
|
conv, err := p.New(converter.DocumentContext{})
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
b, err := conv.Convert(converter.RenderContext{Src: []byte(content)})
|
b, err := conv.Convert(converter.RenderContext{Src: []byte(content), GetRenderer: getRenderer})
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
|
|
||||||
return string(b.Bytes())
|
return string(b.Bytes())
|
||||||
|
@ -391,7 +410,7 @@ LINE5
|
||||||
// TODO(bep) there is a whitespace mismatch (\n) between this and the highlight template func.
|
// TODO(bep) there is a whitespace mismatch (\n) between this and the highlight template func.
|
||||||
c.Assert(result, qt.Equals, "<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"line\"><span class=\"cl\"><span class=\"nb\">echo</span> <span class=\"s2\">"Hugo Rocks!"</span>\n</span></span></code></pre></div>")
|
c.Assert(result, qt.Equals, "<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"line\"><span class=\"cl\"><span class=\"nb\">echo</span> <span class=\"s2\">"Hugo Rocks!"</span>\n</span></span></code></pre></div>")
|
||||||
result = convertForConfig(c, cfg, `echo "Hugo Rocks!"`, "unknown")
|
result = convertForConfig(c, cfg, `echo "Hugo Rocks!"`, "unknown")
|
||||||
c.Assert(result, qt.Equals, "<pre tabindex=\"0\"><code class=\"language-unknown\" data-lang=\"unknown\">echo "Hugo Rocks!"\n</code></pre>")
|
c.Assert(result, qt.Equals, "<pre tabindex=\"0\"><code class=\"language-unknown\" data-lang=\"unknown\">echo "Hugo Rocks!"\n</code></pre>")
|
||||||
})
|
})
|
||||||
|
|
||||||
c.Run("Highlight lines, default config", func(c *qt.C) {
|
c.Run("Highlight lines, default config", func(c *qt.C) {
|
||||||
|
|
|
@ -36,12 +36,12 @@ func TestAttributeExclusion(t *testing.T) {
|
||||||
---
|
---
|
||||||
title: "p1"
|
title: "p1"
|
||||||
---
|
---
|
||||||
## Heading {class="a" onclick="alert('heading')" linenos="inline"}
|
## Heading {class="a" onclick="alert('heading')"}
|
||||||
|
|
||||||
> Blockquote
|
> Blockquote
|
||||||
{class="b" ondblclick="alert('blockquote')" LINENOS="inline"}
|
{class="b" ondblclick="alert('blockquote')"}
|
||||||
|
|
||||||
~~~bash {id="c" onmouseover="alert('code fence')"}
|
~~~bash {id="c" onmouseover="alert('code fence')" LINENOS=true}
|
||||||
foo
|
foo
|
||||||
~~~
|
~~~
|
||||||
-- layouts/_default/single.html --
|
-- layouts/_default/single.html --
|
||||||
|
@ -96,6 +96,63 @@ title: "p1"
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAttributesDefaultRenderer(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
files := `
|
||||||
|
-- content/p1.md --
|
||||||
|
---
|
||||||
|
title: "p1"
|
||||||
|
---
|
||||||
|
## Heading Attribute Which Needs Escaping { class="a < b" }
|
||||||
|
-- layouts/_default/single.html --
|
||||||
|
{{ .Content }}
|
||||||
|
`
|
||||||
|
|
||||||
|
b := hugolib.NewIntegrationTestBuilder(
|
||||||
|
hugolib.IntegrationTestConfig{
|
||||||
|
T: t,
|
||||||
|
TxtarString: files,
|
||||||
|
NeedsOsFS: false,
|
||||||
|
},
|
||||||
|
).Build()
|
||||||
|
|
||||||
|
b.AssertFileContent("public/p1/index.html", `
|
||||||
|
class="a < b"
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue 9558.
|
||||||
|
func TestAttributesHookNoEscape(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
files := `
|
||||||
|
-- content/p1.md --
|
||||||
|
---
|
||||||
|
title: "p1"
|
||||||
|
---
|
||||||
|
## Heading Attribute Which Needs Escaping { class="Smith & Wesson" }
|
||||||
|
-- layouts/_default/_markup/render-heading.html --
|
||||||
|
plain: |{{- range $k, $v := .Attributes -}}{{ $k }}: {{ $v }}|{{ end }}|
|
||||||
|
safeHTML: |{{- range $k, $v := .Attributes -}}{{ $k }}: {{ $v | safeHTML }}|{{ end }}|
|
||||||
|
-- layouts/_default/single.html --
|
||||||
|
{{ .Content }}
|
||||||
|
`
|
||||||
|
|
||||||
|
b := hugolib.NewIntegrationTestBuilder(
|
||||||
|
hugolib.IntegrationTestConfig{
|
||||||
|
T: t,
|
||||||
|
TxtarString: files,
|
||||||
|
NeedsOsFS: false,
|
||||||
|
},
|
||||||
|
).Build()
|
||||||
|
|
||||||
|
b.AssertFileContent("public/p1/index.html", `
|
||||||
|
plain: |class: Smith & Wesson|id: heading-attribute-which-needs-escaping|
|
||||||
|
safeHTML: |class: Smith & Wesson|id: heading-attribute-which-needs-escaping|
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
// Issue 9504
|
// Issue 9504
|
||||||
func TestLinkInTitle(t *testing.T) {
|
func TestLinkInTitle(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
@ -132,6 +189,84 @@ title: "p1"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHighlight(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
files := `
|
||||||
|
-- config.toml --
|
||||||
|
[markup]
|
||||||
|
[markup.highlight]
|
||||||
|
anchorLineNos = false
|
||||||
|
codeFences = true
|
||||||
|
guessSyntax = false
|
||||||
|
hl_Lines = ''
|
||||||
|
lineAnchors = ''
|
||||||
|
lineNoStart = 1
|
||||||
|
lineNos = false
|
||||||
|
lineNumbersInTable = true
|
||||||
|
noClasses = false
|
||||||
|
style = 'monokai'
|
||||||
|
tabWidth = 4
|
||||||
|
-- layouts/_default/single.html --
|
||||||
|
{{ .Content }}
|
||||||
|
-- content/p1.md --
|
||||||
|
---
|
||||||
|
title: "p1"
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code Fences
|
||||||
|
|
||||||
|
§§§bash
|
||||||
|
LINE1
|
||||||
|
§§§
|
||||||
|
|
||||||
|
## Code Fences No Lexer
|
||||||
|
|
||||||
|
§§§moo
|
||||||
|
LINE1
|
||||||
|
§§§
|
||||||
|
|
||||||
|
## Code Fences Simple Attributes
|
||||||
|
|
||||||
|
§§A§bash { .myclass id="myid" }
|
||||||
|
LINE1
|
||||||
|
§§A§
|
||||||
|
|
||||||
|
## Code Fences Line Numbers
|
||||||
|
|
||||||
|
§§§bash {linenos=table,hl_lines=[8,"15-17"],linenostart=199}
|
||||||
|
LINE1
|
||||||
|
LINE2
|
||||||
|
LINE3
|
||||||
|
LINE4
|
||||||
|
LINE5
|
||||||
|
LINE6
|
||||||
|
LINE7
|
||||||
|
LINE8
|
||||||
|
§§§
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
// Code fences
|
||||||
|
files = strings.ReplaceAll(files, "§§§", "```")
|
||||||
|
|
||||||
|
b := hugolib.NewIntegrationTestBuilder(
|
||||||
|
hugolib.IntegrationTestConfig{
|
||||||
|
T: t,
|
||||||
|
TxtarString: files,
|
||||||
|
},
|
||||||
|
).Build()
|
||||||
|
|
||||||
|
b.AssertFileContent("public/p1/index.html",
|
||||||
|
"<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"line\"><span class=\"cl\">LINE1\n</span></span></code></pre></div>",
|
||||||
|
"Code Fences No Lexer</h2>\n<pre tabindex=\"0\"><code class=\"language-moo\" data-lang=\"moo\">LINE1\n</code></pre>",
|
||||||
|
"lnt",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkRenderHooks(b *testing.B) {
|
func BenchmarkRenderHooks(b *testing.B) {
|
||||||
files := `
|
files := `
|
||||||
-- config.toml --
|
-- config.toml --
|
||||||
|
|
81
markup/goldmark/internal/render/context.go
Normal file
81
markup/goldmark/internal/render/context.go
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
// 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 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 render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math/bits"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/identity"
|
||||||
|
"github.com/gohugoio/hugo/markup/converter"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BufWriter struct {
|
||||||
|
*bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxInt = 1<<(bits.UintSize-1) - 1
|
||||||
|
|
||||||
|
func (b *BufWriter) Available() int {
|
||||||
|
return maxInt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BufWriter) Buffered() int {
|
||||||
|
return b.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BufWriter) Flush() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Context struct {
|
||||||
|
*BufWriter
|
||||||
|
positions []int
|
||||||
|
ContextData
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) PushPos(n int) {
|
||||||
|
ctx.positions = append(ctx.positions, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) PopPos() int {
|
||||||
|
i := len(ctx.positions) - 1
|
||||||
|
p := ctx.positions[i]
|
||||||
|
ctx.positions = ctx.positions[:i]
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContextData interface {
|
||||||
|
RenderContext() converter.RenderContext
|
||||||
|
DocumentContext() converter.DocumentContext
|
||||||
|
AddIdentity(id identity.Provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RenderContextDataHolder struct {
|
||||||
|
Rctx converter.RenderContext
|
||||||
|
Dctx converter.DocumentContext
|
||||||
|
IDs identity.Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *RenderContextDataHolder) RenderContext() converter.RenderContext {
|
||||||
|
return ctx.Rctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *RenderContextDataHolder) DocumentContext() converter.DocumentContext {
|
||||||
|
return ctx.Dctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *RenderContextDataHolder) AddIdentity(id identity.Provider) {
|
||||||
|
ctx.IDs.Add(id)
|
||||||
|
}
|
|
@ -16,11 +16,10 @@ package goldmark
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/spf13/cast"
|
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/markup/converter/hooks"
|
"github.com/gohugoio/hugo/markup/converter/hooks"
|
||||||
|
"github.com/gohugoio/hugo/markup/goldmark/internal/render"
|
||||||
|
"github.com/gohugoio/hugo/markup/internal/attributes"
|
||||||
|
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
"github.com/yuin/goldmark/ast"
|
"github.com/yuin/goldmark/ast"
|
||||||
|
@ -44,28 +43,6 @@ func newLinks() goldmark.Extender {
|
||||||
return &links{}
|
return &links{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type attributesHolder struct {
|
|
||||||
// What we get from Goldmark.
|
|
||||||
astAttributes []ast.Attribute
|
|
||||||
|
|
||||||
// What we send to the the render hooks.
|
|
||||||
attributesInit sync.Once
|
|
||||||
attributes map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *attributesHolder) Attributes() map[string]string {
|
|
||||||
a.attributesInit.Do(func() {
|
|
||||||
a.attributes = make(map[string]string)
|
|
||||||
for _, attr := range a.astAttributes {
|
|
||||||
if strings.HasPrefix(string(attr.Name), "on") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
a.attributes[string(attr.Name)] = string(util.EscapeHTML(attr.Value.([]byte)))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return a.attributes
|
|
||||||
}
|
|
||||||
|
|
||||||
type linkContext struct {
|
type linkContext struct {
|
||||||
page interface{}
|
page interface{}
|
||||||
destination string
|
destination string
|
||||||
|
@ -104,7 +81,7 @@ type headingContext struct {
|
||||||
anchor string
|
anchor string
|
||||||
text string
|
text string
|
||||||
plainText string
|
plainText string
|
||||||
*attributesHolder
|
*attributes.AttributesHolder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx headingContext) Page() interface{} {
|
func (ctx headingContext) Page() interface{} {
|
||||||
|
@ -143,52 +120,17 @@ func (r *hookedRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer)
|
||||||
reg.Register(ast.KindHeading, r.renderHeading)
|
reg.Register(ast.KindHeading, r.renderHeading)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *hookedRenderer) renderAttributesForNode(w util.BufWriter, node ast.Node) {
|
|
||||||
renderAttributes(w, false, node.Attributes()...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attributes with special meaning that does not make sense to render in HTML.
|
|
||||||
var attributeExcludes = map[string]bool{
|
|
||||||
"hl_lines": true,
|
|
||||||
"hl_style": true,
|
|
||||||
"linenos": true,
|
|
||||||
"linenostart": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderAttributes(w util.BufWriter, skipClass bool, attributes ...ast.Attribute) {
|
|
||||||
for _, attr := range attributes {
|
|
||||||
if skipClass && bytes.Equal(attr.Name, []byte("class")) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
a := strings.ToLower(string(attr.Name))
|
|
||||||
if attributeExcludes[a] || strings.HasPrefix(a, "on") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = w.WriteString(" ")
|
|
||||||
_, _ = w.Write(attr.Name)
|
|
||||||
_, _ = w.WriteString(`="`)
|
|
||||||
|
|
||||||
switch v := attr.Value.(type) {
|
|
||||||
case []byte:
|
|
||||||
_, _ = w.Write(util.EscapeHTML(v))
|
|
||||||
default:
|
|
||||||
w.WriteString(cast.ToString(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = w.WriteByte('"')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
n := node.(*ast.Image)
|
n := node.(*ast.Image)
|
||||||
var h hooks.Renderers
|
var lr hooks.LinkRenderer
|
||||||
|
|
||||||
ctx, ok := w.(*renderContext)
|
ctx, ok := w.(*render.Context)
|
||||||
if ok {
|
if ok {
|
||||||
h = ctx.RenderContext().RenderHooks
|
h := ctx.RenderContext().GetRenderer(hooks.ImageRendererType, nil)
|
||||||
ok = h.ImageRenderer != nil
|
ok = h != nil
|
||||||
|
if ok {
|
||||||
|
lr = h.(hooks.LinkRenderer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -197,15 +139,15 @@ func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.N
|
||||||
|
|
||||||
if entering {
|
if entering {
|
||||||
// Store the current pos so we can capture the rendered text.
|
// Store the current pos so we can capture the rendered text.
|
||||||
ctx.pushPos(ctx.Buffer.Len())
|
ctx.PushPos(ctx.Buffer.Len())
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pos := ctx.popPos()
|
pos := ctx.PopPos()
|
||||||
text := ctx.Buffer.Bytes()[pos:]
|
text := ctx.Buffer.Bytes()[pos:]
|
||||||
ctx.Buffer.Truncate(pos)
|
ctx.Buffer.Truncate(pos)
|
||||||
|
|
||||||
err := h.ImageRenderer.RenderLink(
|
err := lr.RenderLink(
|
||||||
w,
|
w,
|
||||||
linkContext{
|
linkContext{
|
||||||
page: ctx.DocumentContext().Document,
|
page: ctx.DocumentContext().Document,
|
||||||
|
@ -216,7 +158,7 @@ func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.N
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
ctx.AddIdentity(h.ImageRenderer)
|
ctx.AddIdentity(lr)
|
||||||
|
|
||||||
return ast.WalkContinue, err
|
return ast.WalkContinue, err
|
||||||
}
|
}
|
||||||
|
@ -250,12 +192,15 @@ func (r *hookedRenderer) renderImageDefault(w util.BufWriter, source []byte, nod
|
||||||
|
|
||||||
func (r *hookedRenderer) renderLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *hookedRenderer) renderLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
n := node.(*ast.Link)
|
n := node.(*ast.Link)
|
||||||
var h hooks.Renderers
|
var lr hooks.LinkRenderer
|
||||||
|
|
||||||
ctx, ok := w.(*renderContext)
|
ctx, ok := w.(*render.Context)
|
||||||
if ok {
|
if ok {
|
||||||
h = ctx.RenderContext().RenderHooks
|
h := ctx.RenderContext().GetRenderer(hooks.LinkRendererType, nil)
|
||||||
ok = h.LinkRenderer != nil
|
ok = h != nil
|
||||||
|
if ok {
|
||||||
|
lr = h.(hooks.LinkRenderer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -264,15 +209,15 @@ func (r *hookedRenderer) renderLink(w util.BufWriter, source []byte, node ast.No
|
||||||
|
|
||||||
if entering {
|
if entering {
|
||||||
// Store the current pos so we can capture the rendered text.
|
// Store the current pos so we can capture the rendered text.
|
||||||
ctx.pushPos(ctx.Buffer.Len())
|
ctx.PushPos(ctx.Buffer.Len())
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pos := ctx.popPos()
|
pos := ctx.PopPos()
|
||||||
text := ctx.Buffer.Bytes()[pos:]
|
text := ctx.Buffer.Bytes()[pos:]
|
||||||
ctx.Buffer.Truncate(pos)
|
ctx.Buffer.Truncate(pos)
|
||||||
|
|
||||||
err := h.LinkRenderer.RenderLink(
|
err := lr.RenderLink(
|
||||||
w,
|
w,
|
||||||
linkContext{
|
linkContext{
|
||||||
page: ctx.DocumentContext().Document,
|
page: ctx.DocumentContext().Document,
|
||||||
|
@ -286,7 +231,7 @@ func (r *hookedRenderer) renderLink(w util.BufWriter, source []byte, node ast.No
|
||||||
// TODO(bep) I have a working branch that fixes these rather confusing identity types,
|
// TODO(bep) I have a working branch that fixes these rather confusing identity types,
|
||||||
// but for now it's important that it's not .GetIdentity() that's added here,
|
// but for now it's important that it's not .GetIdentity() that's added here,
|
||||||
// to make sure we search the entire chain on changes.
|
// to make sure we search the entire chain on changes.
|
||||||
ctx.AddIdentity(h.LinkRenderer)
|
ctx.AddIdentity(lr)
|
||||||
|
|
||||||
return ast.WalkContinue, err
|
return ast.WalkContinue, err
|
||||||
}
|
}
|
||||||
|
@ -319,12 +264,15 @@ func (r *hookedRenderer) renderAutoLink(w util.BufWriter, source []byte, node as
|
||||||
}
|
}
|
||||||
|
|
||||||
n := node.(*ast.AutoLink)
|
n := node.(*ast.AutoLink)
|
||||||
var h hooks.Renderers
|
var lr hooks.LinkRenderer
|
||||||
|
|
||||||
ctx, ok := w.(*renderContext)
|
ctx, ok := w.(*render.Context)
|
||||||
if ok {
|
if ok {
|
||||||
h = ctx.RenderContext().RenderHooks
|
h := ctx.RenderContext().GetRenderer(hooks.LinkRendererType, nil)
|
||||||
ok = h.LinkRenderer != nil
|
ok = h != nil
|
||||||
|
if ok {
|
||||||
|
lr = h.(hooks.LinkRenderer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -337,7 +285,7 @@ func (r *hookedRenderer) renderAutoLink(w util.BufWriter, source []byte, node as
|
||||||
url = "mailto:" + url
|
url = "mailto:" + url
|
||||||
}
|
}
|
||||||
|
|
||||||
err := h.LinkRenderer.RenderLink(
|
err := lr.RenderLink(
|
||||||
w,
|
w,
|
||||||
linkContext{
|
linkContext{
|
||||||
page: ctx.DocumentContext().Document,
|
page: ctx.DocumentContext().Document,
|
||||||
|
@ -350,7 +298,7 @@ func (r *hookedRenderer) renderAutoLink(w util.BufWriter, source []byte, node as
|
||||||
// TODO(bep) I have a working branch that fixes these rather confusing identity types,
|
// TODO(bep) I have a working branch that fixes these rather confusing identity types,
|
||||||
// but for now it's important that it's not .GetIdentity() that's added here,
|
// but for now it's important that it's not .GetIdentity() that's added here,
|
||||||
// to make sure we search the entire chain on changes.
|
// to make sure we search the entire chain on changes.
|
||||||
ctx.AddIdentity(h.LinkRenderer)
|
ctx.AddIdentity(lr)
|
||||||
|
|
||||||
return ast.WalkContinue, err
|
return ast.WalkContinue, err
|
||||||
}
|
}
|
||||||
|
@ -383,12 +331,15 @@ func (r *hookedRenderer) renderAutoLinkDefault(w util.BufWriter, source []byte,
|
||||||
|
|
||||||
func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
n := node.(*ast.Heading)
|
n := node.(*ast.Heading)
|
||||||
var h hooks.Renderers
|
var hr hooks.HeadingRenderer
|
||||||
|
|
||||||
ctx, ok := w.(*renderContext)
|
ctx, ok := w.(*render.Context)
|
||||||
if ok {
|
if ok {
|
||||||
h = ctx.RenderContext().RenderHooks
|
h := ctx.RenderContext().GetRenderer(hooks.HeadingRendererType, nil)
|
||||||
ok = h.HeadingRenderer != nil
|
ok = h != nil
|
||||||
|
if ok {
|
||||||
|
hr = h.(hooks.HeadingRenderer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -397,11 +348,11 @@ func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast
|
||||||
|
|
||||||
if entering {
|
if entering {
|
||||||
// Store the current pos so we can capture the rendered text.
|
// Store the current pos so we can capture the rendered text.
|
||||||
ctx.pushPos(ctx.Buffer.Len())
|
ctx.PushPos(ctx.Buffer.Len())
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pos := ctx.popPos()
|
pos := ctx.PopPos()
|
||||||
text := ctx.Buffer.Bytes()[pos:]
|
text := ctx.Buffer.Bytes()[pos:]
|
||||||
ctx.Buffer.Truncate(pos)
|
ctx.Buffer.Truncate(pos)
|
||||||
// All ast.Heading nodes are guaranteed to have an attribute called "id"
|
// All ast.Heading nodes are guaranteed to have an attribute called "id"
|
||||||
|
@ -409,7 +360,7 @@ func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast
|
||||||
anchori, _ := n.AttributeString("id")
|
anchori, _ := n.AttributeString("id")
|
||||||
anchor := anchori.([]byte)
|
anchor := anchori.([]byte)
|
||||||
|
|
||||||
err := h.HeadingRenderer.RenderHeading(
|
err := hr.RenderHeading(
|
||||||
w,
|
w,
|
||||||
headingContext{
|
headingContext{
|
||||||
page: ctx.DocumentContext().Document,
|
page: ctx.DocumentContext().Document,
|
||||||
|
@ -417,11 +368,11 @@ func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast
|
||||||
anchor: string(anchor),
|
anchor: string(anchor),
|
||||||
text: string(text),
|
text: string(text),
|
||||||
plainText: string(n.Text(source)),
|
plainText: string(n.Text(source)),
|
||||||
attributesHolder: &attributesHolder{astAttributes: n.Attributes()},
|
AttributesHolder: attributes.New(n.Attributes(), attributes.AttributesOwnerGeneral),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
ctx.AddIdentity(h.HeadingRenderer)
|
ctx.AddIdentity(hr)
|
||||||
|
|
||||||
return ast.WalkContinue, err
|
return ast.WalkContinue, err
|
||||||
}
|
}
|
||||||
|
@ -432,7 +383,7 @@ func (r *hookedRenderer) renderHeadingDefault(w util.BufWriter, source []byte, n
|
||||||
_, _ = w.WriteString("<h")
|
_, _ = w.WriteString("<h")
|
||||||
_ = w.WriteByte("0123456"[n.Level])
|
_ = w.WriteByte("0123456"[n.Level])
|
||||||
if n.Attributes() != nil {
|
if n.Attributes() != nil {
|
||||||
r.renderAttributesForNode(w, node)
|
attributes.RenderASTAttributes(w, node.Attributes()...)
|
||||||
}
|
}
|
||||||
_ = w.WriteByte('>')
|
_ = w.WriteByte('>')
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/markup/converter/hooks"
|
||||||
"github.com/gohugoio/hugo/markup/markup_config"
|
"github.com/gohugoio/hugo/markup/markup_config"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/loggers"
|
"github.com/gohugoio/hugo/common/loggers"
|
||||||
|
@ -27,6 +28,8 @@ import (
|
||||||
qt "github.com/frankban/quicktest"
|
qt "github.com/frankban/quicktest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var nopGetRenderer = func(t hooks.RendererType, id interface{}) interface{} { return nil }
|
||||||
|
|
||||||
func TestToc(t *testing.T) {
|
func TestToc(t *testing.T) {
|
||||||
c := qt.New(t)
|
c := qt.New(t)
|
||||||
|
|
||||||
|
@ -58,7 +61,7 @@ And then some.
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
conv, err := p.New(converter.DocumentContext{})
|
conv, err := p.New(converter.DocumentContext{})
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
b, err := conv.Convert(converter.RenderContext{Src: []byte(content), RenderTOC: true})
|
b, err := conv.Convert(converter.RenderContext{Src: []byte(content), RenderTOC: true, GetRenderer: nopGetRenderer})
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
got := b.(converter.TableOfContentsProvider).TableOfContents().ToHTML(2, 3, false)
|
got := b.(converter.TableOfContentsProvider).TableOfContents().ToHTML(2, 3, false)
|
||||||
c.Assert(got, qt.Equals, `<nav id="TableOfContents">
|
c.Assert(got, qt.Equals, `<nav id="TableOfContents">
|
||||||
|
@ -108,7 +111,7 @@ func TestEscapeToc(t *testing.T) {
|
||||||
"# `echo codeblock`",
|
"# `echo codeblock`",
|
||||||
}, "\n")
|
}, "\n")
|
||||||
// content := ""
|
// content := ""
|
||||||
b, err := safeConv.Convert(converter.RenderContext{Src: []byte(content), RenderTOC: true})
|
b, err := safeConv.Convert(converter.RenderContext{Src: []byte(content), RenderTOC: true, GetRenderer: nopGetRenderer})
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
got := b.(converter.TableOfContentsProvider).TableOfContents().ToHTML(1, 2, false)
|
got := b.(converter.TableOfContentsProvider).TableOfContents().ToHTML(1, 2, false)
|
||||||
c.Assert(got, qt.Equals, `<nav id="TableOfContents">
|
c.Assert(got, qt.Equals, `<nav id="TableOfContents">
|
||||||
|
@ -120,7 +123,7 @@ func TestEscapeToc(t *testing.T) {
|
||||||
</ul>
|
</ul>
|
||||||
</nav>`, qt.Commentf(got))
|
</nav>`, qt.Commentf(got))
|
||||||
|
|
||||||
b, err = unsafeConv.Convert(converter.RenderContext{Src: []byte(content), RenderTOC: true})
|
b, err = unsafeConv.Convert(converter.RenderContext{Src: []byte(content), RenderTOC: true, GetRenderer: nopGetRenderer})
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
got = b.(converter.TableOfContentsProvider).TableOfContents().ToHTML(1, 2, false)
|
got = b.(converter.TableOfContentsProvider).TableOfContents().ToHTML(1, 2, false)
|
||||||
c.Assert(got, qt.Equals, `<nav id="TableOfContents">
|
c.Assert(got, qt.Equals, `<nav id="TableOfContents">
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/alecthomas/chroma/formatters/html"
|
"github.com/alecthomas/chroma/formatters/html"
|
||||||
|
"github.com/spf13/cast"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/config"
|
"github.com/gohugoio/hugo/config"
|
||||||
|
|
||||||
|
@ -46,6 +47,9 @@ type Config struct {
|
||||||
// Use inline CSS styles.
|
// Use inline CSS styles.
|
||||||
NoClasses bool
|
NoClasses bool
|
||||||
|
|
||||||
|
// No highlighting.
|
||||||
|
NoHl bool
|
||||||
|
|
||||||
// When set, line numbers will be printed.
|
// When set, line numbers will be printed.
|
||||||
LineNos bool
|
LineNos bool
|
||||||
LineNumbersInTable bool
|
LineNumbersInTable bool
|
||||||
|
@ -60,6 +64,9 @@ type Config struct {
|
||||||
// A space separated list of line numbers, e.g. “3-8 10-20”.
|
// A space separated list of line numbers, e.g. “3-8 10-20”.
|
||||||
Hl_Lines string
|
Hl_Lines string
|
||||||
|
|
||||||
|
// A parsed and ready to use list of line ranges.
|
||||||
|
HL_lines_parsed [][2]int
|
||||||
|
|
||||||
// TabWidth sets the number of characters for a tab. Defaults to 4.
|
// TabWidth sets the number of characters for a tab. Defaults to 4.
|
||||||
TabWidth int
|
TabWidth int
|
||||||
|
|
||||||
|
@ -80,9 +87,19 @@ func (cfg Config) ToHTMLOptions() []html.Option {
|
||||||
html.LinkableLineNumbers(cfg.AnchorLineNos, lineAnchors),
|
html.LinkableLineNumbers(cfg.AnchorLineNos, lineAnchors),
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Hl_Lines != "" {
|
if cfg.Hl_Lines != "" || cfg.HL_lines_parsed != nil {
|
||||||
ranges, err := hlLinesToRanges(cfg.LineNoStart, cfg.Hl_Lines)
|
var ranges [][2]int
|
||||||
if err == nil {
|
if cfg.HL_lines_parsed != nil {
|
||||||
|
ranges = cfg.HL_lines_parsed
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
ranges, err = hlLinesToRanges(cfg.LineNoStart, cfg.Hl_Lines)
|
||||||
|
if err != nil {
|
||||||
|
ranges = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ranges != nil {
|
||||||
options = append(options, html.HighlightLines(ranges))
|
options = append(options, html.HighlightLines(ranges))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,14 +107,32 @@ func (cfg Config) ToHTMLOptions() []html.Option {
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applyOptions(opts interface{}, cfg *Config) error {
|
||||||
|
if opts == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
switch vv := opts.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
return applyOptionsFromMap(vv, cfg)
|
||||||
|
case string:
|
||||||
|
return applyOptionsFromString(vv, cfg)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func applyOptionsFromString(opts string, cfg *Config) error {
|
func applyOptionsFromString(opts string, cfg *Config) error {
|
||||||
optsm, err := parseOptions(opts)
|
optsm, err := parseHightlightOptions(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return mapstructure.WeakDecode(optsm, cfg)
|
return mapstructure.WeakDecode(optsm, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applyOptionsFromMap(optsm map[string]interface{}, cfg *Config) error {
|
||||||
|
normalizeHighlightOptions(optsm)
|
||||||
|
return mapstructure.WeakDecode(optsm, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
// ApplyLegacyConfig applies legacy config from back when we had
|
// ApplyLegacyConfig applies legacy config from back when we had
|
||||||
// Pygments.
|
// Pygments.
|
||||||
func ApplyLegacyConfig(cfg config.Provider, conf *Config) error {
|
func ApplyLegacyConfig(cfg config.Provider, conf *Config) error {
|
||||||
|
@ -128,7 +163,7 @@ func ApplyLegacyConfig(cfg config.Provider, conf *Config) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseOptions(in string) (map[string]interface{}, error) {
|
func parseHightlightOptions(in string) (map[string]interface{}, error) {
|
||||||
in = strings.Trim(in, " ")
|
in = strings.Trim(in, " ")
|
||||||
opts := make(map[string]interface{})
|
opts := make(map[string]interface{})
|
||||||
|
|
||||||
|
@ -142,19 +177,57 @@ func parseOptions(in string) (map[string]interface{}, error) {
|
||||||
if len(keyVal) != 2 {
|
if len(keyVal) != 2 {
|
||||||
return opts, fmt.Errorf("invalid Highlight option: %s", key)
|
return opts, fmt.Errorf("invalid Highlight option: %s", key)
|
||||||
}
|
}
|
||||||
if key == "linenos" {
|
|
||||||
opts[key] = keyVal[1] != "false"
|
|
||||||
if keyVal[1] == "table" || keyVal[1] == "inline" {
|
|
||||||
opts["lineNumbersInTable"] = keyVal[1] == "table"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
opts[key] = keyVal[1]
|
opts[key] = keyVal[1]
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
normalizeHighlightOptions(opts)
|
||||||
|
|
||||||
return opts, nil
|
return opts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeHighlightOptions(m map[string]interface{}) {
|
||||||
|
if m == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
lineNosKey = "linenos"
|
||||||
|
hlLinesKey = "hl_lines"
|
||||||
|
linosStartKey = "linenostart"
|
||||||
|
noHlKey = "nohl"
|
||||||
|
)
|
||||||
|
|
||||||
|
baseLineNumber := 1
|
||||||
|
if v, ok := m[linosStartKey]; ok {
|
||||||
|
baseLineNumber = cast.ToInt(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range m {
|
||||||
|
switch k {
|
||||||
|
case noHlKey:
|
||||||
|
m[noHlKey] = cast.ToBool(v)
|
||||||
|
case lineNosKey:
|
||||||
|
if v == "table" || v == "inline" {
|
||||||
|
m["lineNumbersInTable"] = v == "table"
|
||||||
|
}
|
||||||
|
if vs, ok := v.(string); ok {
|
||||||
|
m[k] = vs != "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
case hlLinesKey:
|
||||||
|
if hlRanges, ok := v.([][2]int); ok {
|
||||||
|
for i := range hlRanges {
|
||||||
|
hlRanges[i][0] += baseLineNumber
|
||||||
|
hlRanges[i][1] += baseLineNumber
|
||||||
|
}
|
||||||
|
delete(m, k)
|
||||||
|
m[k+"_parsed"] = hlRanges
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// startLine compensates for https://github.com/alecthomas/chroma/issues/30
|
// startLine compensates for https://github.com/alecthomas/chroma/issues/30
|
||||||
func hlLinesToRanges(startLine int, s string) ([][2]int, error) {
|
func hlLinesToRanges(startLine int, s string) ([][2]int, error) {
|
||||||
var ranges [][2]int
|
var ranges [][2]int
|
||||||
|
|
|
@ -16,47 +16,155 @@ package highlight
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
gohtml "html"
|
gohtml "html"
|
||||||
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/alecthomas/chroma"
|
"github.com/alecthomas/chroma"
|
||||||
"github.com/alecthomas/chroma/formatters/html"
|
"github.com/alecthomas/chroma/formatters/html"
|
||||||
"github.com/alecthomas/chroma/lexers"
|
"github.com/alecthomas/chroma/lexers"
|
||||||
"github.com/alecthomas/chroma/styles"
|
"github.com/alecthomas/chroma/styles"
|
||||||
hl "github.com/yuin/goldmark-highlighting"
|
"github.com/gohugoio/hugo/common/hugio"
|
||||||
|
"github.com/gohugoio/hugo/identity"
|
||||||
|
"github.com/gohugoio/hugo/markup/converter/hooks"
|
||||||
|
"github.com/gohugoio/hugo/markup/internal/attributes"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Markdown attributes used by the Chroma hightlighter.
|
||||||
|
var chromaHightlightProcessingAttributes = map[string]bool{
|
||||||
|
"anchorLineNos": true,
|
||||||
|
"guessSyntax": true,
|
||||||
|
"hl_Lines": true,
|
||||||
|
"lineAnchors": true,
|
||||||
|
"lineNos": true,
|
||||||
|
"lineNoStart": true,
|
||||||
|
"lineNumbersInTable": true,
|
||||||
|
"noClasses": true,
|
||||||
|
"style": true,
|
||||||
|
"tabWidth": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for k, v := range chromaHightlightProcessingAttributes {
|
||||||
|
chromaHightlightProcessingAttributes[strings.ToLower(k)] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func New(cfg Config) Highlighter {
|
func New(cfg Config) Highlighter {
|
||||||
return Highlighter{
|
return chromaHighlighter{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Highlighter struct {
|
type Highlighter interface {
|
||||||
|
Highlight(code, lang string, opts interface{}) (string, error)
|
||||||
|
HighlightCodeBlock(ctx hooks.CodeblockContext, opts interface{}) (HightlightResult, error)
|
||||||
|
hooks.CodeBlockRenderer
|
||||||
|
}
|
||||||
|
|
||||||
|
type chromaHighlighter struct {
|
||||||
cfg Config
|
cfg Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Highlighter) Highlight(code, lang, optsStr string) (string, error) {
|
func (h chromaHighlighter) Highlight(code, lang string, opts interface{}) (string, error) {
|
||||||
if optsStr == "" {
|
|
||||||
return highlight(code, lang, h.cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := h.cfg
|
cfg := h.cfg
|
||||||
if err := applyOptionsFromString(optsStr, &cfg); err != nil {
|
if err := applyOptions(opts, &cfg); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var b strings.Builder
|
||||||
|
|
||||||
|
if err := highlight(&b, code, lang, nil, cfg); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return highlight(code, lang, cfg)
|
return b.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func highlight(code, lang string, cfg Config) (string, error) {
|
func (h chromaHighlighter) HighlightCodeBlock(ctx hooks.CodeblockContext, opts interface{}) (HightlightResult, error) {
|
||||||
w := &strings.Builder{}
|
cfg := h.cfg
|
||||||
|
|
||||||
|
var b strings.Builder
|
||||||
|
|
||||||
|
attributes := ctx.(hooks.AttributesOptionsSliceProvider).AttributesSlice()
|
||||||
|
options := ctx.Options()
|
||||||
|
|
||||||
|
if err := applyOptionsFromMap(options, &cfg); err != nil {
|
||||||
|
return HightlightResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply these last so the user can override them.
|
||||||
|
if err := applyOptions(opts, &cfg); err != nil {
|
||||||
|
return HightlightResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := highlight(&b, ctx.Code(), ctx.Lang(), attributes, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return HightlightResult{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return HightlightResult{
|
||||||
|
Body: template.HTML(b.String()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h chromaHighlighter) RenderCodeblock(w hugio.FlexiWriter, ctx hooks.CodeblockContext) error {
|
||||||
|
cfg := h.cfg
|
||||||
|
attributes := ctx.(hooks.AttributesOptionsSliceProvider).AttributesSlice()
|
||||||
|
|
||||||
|
if err := applyOptionsFromMap(ctx.Options(), &cfg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return highlight(w, ctx.Code(), ctx.Lang(), attributes, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
var id = identity.NewPathIdentity("chroma", "highlight")
|
||||||
|
|
||||||
|
func (h chromaHighlighter) GetIdentity() identity.Identity {
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
type HightlightResult struct {
|
||||||
|
Body template.HTML
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h HightlightResult) Highlighted() template.HTML {
|
||||||
|
return h.Body
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h chromaHighlighter) toHighlightOptionsAttributes(ctx hooks.CodeblockContext) (map[string]interface{}, map[string]interface{}) {
|
||||||
|
attributes := ctx.Attributes()
|
||||||
|
if attributes == nil || len(attributes) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
options := make(map[string]interface{})
|
||||||
|
attrs := make(map[string]interface{})
|
||||||
|
|
||||||
|
for k, v := range attributes {
|
||||||
|
klow := strings.ToLower(k)
|
||||||
|
if chromaHightlightProcessingAttributes[klow] {
|
||||||
|
options[klow] = v
|
||||||
|
} else {
|
||||||
|
attrs[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const lineanchorsKey = "lineanchors"
|
||||||
|
if _, found := options[lineanchorsKey]; !found {
|
||||||
|
// Set it to the ordinal.
|
||||||
|
options[lineanchorsKey] = strconv.Itoa(ctx.Ordinal())
|
||||||
|
}
|
||||||
|
return options, attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func highlight(w hugio.FlexiWriter, code, lang string, attributes []attributes.Attribute, cfg Config) error {
|
||||||
var lexer chroma.Lexer
|
var lexer chroma.Lexer
|
||||||
if lang != "" {
|
if lang != "" {
|
||||||
lexer = lexers.Get(lang)
|
lexer = lexers.Get(lang)
|
||||||
}
|
}
|
||||||
|
|
||||||
if lexer == nil && cfg.GuessSyntax {
|
if lexer == nil && (cfg.GuessSyntax && !cfg.NoHl) {
|
||||||
lexer = lexers.Analyse(code)
|
lexer = lexers.Analyse(code)
|
||||||
if lexer == nil {
|
if lexer == nil {
|
||||||
lexer = lexers.Fallback
|
lexer = lexers.Fallback
|
||||||
|
@ -69,7 +177,7 @@ func highlight(code, lang string, cfg Config) (string, error) {
|
||||||
fmt.Fprint(w, wrapper.Start(true, ""))
|
fmt.Fprint(w, wrapper.Start(true, ""))
|
||||||
fmt.Fprint(w, gohtml.EscapeString(code))
|
fmt.Fprint(w, gohtml.EscapeString(code))
|
||||||
fmt.Fprint(w, wrapper.End(true))
|
fmt.Fprint(w, wrapper.End(true))
|
||||||
return w.String(), nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
style := styles.Get(cfg.Style)
|
style := styles.Get(cfg.Style)
|
||||||
|
@ -80,7 +188,7 @@ func highlight(code, lang string, cfg Config) (string, error) {
|
||||||
|
|
||||||
iterator, err := lexer.Tokenise(nil, code)
|
iterator, err := lexer.Tokenise(nil, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
options := cfg.ToHTMLOptions()
|
options := cfg.ToHTMLOptions()
|
||||||
|
@ -88,25 +196,13 @@ func highlight(code, lang string, cfg Config) (string, error) {
|
||||||
|
|
||||||
formatter := html.New(options...)
|
formatter := html.New(options...)
|
||||||
|
|
||||||
fmt.Fprint(w, `<div class="highlight">`)
|
writeDivStart(w, attributes)
|
||||||
if err := formatter.Format(w, style, iterator); err != nil {
|
if err := formatter.Format(w, style, iterator); err != nil {
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprint(w, `</div>`)
|
writeDivEnd(w)
|
||||||
|
|
||||||
return w.String(), nil
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
func GetCodeBlockOptions() func(ctx hl.CodeBlockContext) []html.Option {
|
|
||||||
return func(ctx hl.CodeBlockContext) []html.Option {
|
|
||||||
var language string
|
|
||||||
if l, ok := ctx.Language(); ok {
|
|
||||||
language = string(l)
|
|
||||||
}
|
|
||||||
return []html.Option{
|
|
||||||
getHtmlPreWrapper(language),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPreWrapper(language string) preWrapper {
|
func getPreWrapper(language string) preWrapper {
|
||||||
|
@ -150,3 +246,25 @@ func (p preWrapper) End(code bool) string {
|
||||||
func WritePreEnd(w io.Writer) {
|
func WritePreEnd(w io.Writer) {
|
||||||
fmt.Fprint(w, preEnd)
|
fmt.Fprint(w, preEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeDivStart(w hugio.FlexiWriter, attrs []attributes.Attribute) {
|
||||||
|
w.WriteString(`<div class="highlight`)
|
||||||
|
if attrs != nil {
|
||||||
|
for _, attr := range attrs {
|
||||||
|
if attr.Name == "class" {
|
||||||
|
w.WriteString(" " + attr.ValueString())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, _ = w.WriteString("\"")
|
||||||
|
attributes.RenderAttributes(w, true, attrs...)
|
||||||
|
} else {
|
||||||
|
_, _ = w.WriteString("\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteString(">")
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeDivEnd(w hugio.FlexiWriter) {
|
||||||
|
w.WriteString("</div>")
|
||||||
|
}
|
||||||
|
|
219
markup/internal/attributes/attributes.go
Normal file
219
markup/internal/attributes/attributes.go
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
// 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 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 attributes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/hugio"
|
||||||
|
"github.com/spf13/cast"
|
||||||
|
"github.com/yuin/goldmark/ast"
|
||||||
|
"github.com/yuin/goldmark/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Markdown attributes used as options by the Chroma highlighter.
|
||||||
|
var chromaHightlightProcessingAttributes = map[string]bool{
|
||||||
|
"anchorLineNos": true,
|
||||||
|
"guessSyntax": true,
|
||||||
|
"hl_Lines": true,
|
||||||
|
"lineAnchors": true,
|
||||||
|
"lineNos": true,
|
||||||
|
"lineNoStart": true,
|
||||||
|
"lineNumbersInTable": true,
|
||||||
|
"noClasses": true,
|
||||||
|
"nohl": true,
|
||||||
|
"style": true,
|
||||||
|
"tabWidth": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for k, v := range chromaHightlightProcessingAttributes {
|
||||||
|
chromaHightlightProcessingAttributes[strings.ToLower(k)] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type AttributesOwnerType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
AttributesOwnerGeneral AttributesOwnerType = iota
|
||||||
|
AttributesOwnerCodeBlock
|
||||||
|
)
|
||||||
|
|
||||||
|
func New(astAttributes []ast.Attribute, ownerType AttributesOwnerType) *AttributesHolder {
|
||||||
|
var (
|
||||||
|
attrs []Attribute
|
||||||
|
opts []Attribute
|
||||||
|
)
|
||||||
|
for _, v := range astAttributes {
|
||||||
|
nameLower := strings.ToLower(string(v.Name))
|
||||||
|
if strings.HasPrefix(string(nameLower), "on") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var vv interface{}
|
||||||
|
switch vvv := v.Value.(type) {
|
||||||
|
case bool, float64:
|
||||||
|
vv = vvv
|
||||||
|
case []interface{}:
|
||||||
|
// Highlight line number hlRanges.
|
||||||
|
var hlRanges [][2]int
|
||||||
|
for _, l := range vvv {
|
||||||
|
if ln, ok := l.(float64); ok {
|
||||||
|
hlRanges = append(hlRanges, [2]int{int(ln) - 1, int(ln) - 1})
|
||||||
|
} else if rng, ok := l.([]uint8); ok {
|
||||||
|
slices := strings.Split(string([]byte(rng)), "-")
|
||||||
|
lhs, err := strconv.Atoi(slices[0])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rhs := lhs
|
||||||
|
if len(slices) > 1 {
|
||||||
|
rhs, err = strconv.Atoi(slices[1])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hlRanges = append(hlRanges, [2]int{lhs - 1, rhs - 1})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vv = hlRanges
|
||||||
|
case []byte:
|
||||||
|
// Note that we don't do any HTML escaping here.
|
||||||
|
// We used to do that, but that changed in #9558.
|
||||||
|
// Noww it's up to the templates to decide.
|
||||||
|
vv = string(vvv)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("not implemented: %T", vvv))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ownerType == AttributesOwnerCodeBlock && chromaHightlightProcessingAttributes[nameLower] {
|
||||||
|
attr := Attribute{Name: string(v.Name), Value: vv}
|
||||||
|
opts = append(opts, attr)
|
||||||
|
} else {
|
||||||
|
attr := Attribute{Name: nameLower, Value: vv}
|
||||||
|
attrs = append(attrs, attr)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AttributesHolder{
|
||||||
|
attributes: attrs,
|
||||||
|
options: opts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Attribute struct {
|
||||||
|
Name string
|
||||||
|
Value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Attribute) ValueString() string {
|
||||||
|
return cast.ToString(a.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AttributesHolder struct {
|
||||||
|
// What we get from Goldmark.
|
||||||
|
attributes []Attribute
|
||||||
|
|
||||||
|
// Attributes considered to be an option (code blocks)
|
||||||
|
options []Attribute
|
||||||
|
|
||||||
|
// What we send to the the render hooks.
|
||||||
|
attributesMapInit sync.Once
|
||||||
|
attributesMap map[string]interface{}
|
||||||
|
optionsMapInit sync.Once
|
||||||
|
optionsMap map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Attributes map[string]interface{}
|
||||||
|
|
||||||
|
func (a *AttributesHolder) Attributes() map[string]interface{} {
|
||||||
|
a.attributesMapInit.Do(func() {
|
||||||
|
a.attributesMap = make(map[string]interface{})
|
||||||
|
for _, v := range a.attributes {
|
||||||
|
a.attributesMap[v.Name] = v.Value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return a.attributesMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AttributesHolder) Options() map[string]interface{} {
|
||||||
|
a.optionsMapInit.Do(func() {
|
||||||
|
a.optionsMap = make(map[string]interface{})
|
||||||
|
for _, v := range a.options {
|
||||||
|
a.optionsMap[v.Name] = v.Value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return a.optionsMap
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AttributesHolder) AttributesSlice() []Attribute {
|
||||||
|
return a.attributes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AttributesHolder) OptionsSlice() []Attribute {
|
||||||
|
return a.options
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenderASTAttributes writes the AST attributes to the given as attributes to an HTML element.
|
||||||
|
// This is used by the default HTML renderers, e.g. for headings etc. where no hook template could be found.
|
||||||
|
// This performs HTML esacaping of string attributes.
|
||||||
|
func RenderASTAttributes(w hugio.FlexiWriter, attributes ...ast.Attribute) {
|
||||||
|
for _, attr := range attributes {
|
||||||
|
|
||||||
|
a := strings.ToLower(string(attr.Name))
|
||||||
|
if strings.HasPrefix(a, "on") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = w.WriteString(" ")
|
||||||
|
_, _ = w.Write(attr.Name)
|
||||||
|
_, _ = w.WriteString(`="`)
|
||||||
|
|
||||||
|
switch v := attr.Value.(type) {
|
||||||
|
case []byte:
|
||||||
|
_, _ = w.Write(util.EscapeHTML(v))
|
||||||
|
default:
|
||||||
|
w.WriteString(cast.ToString(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = w.WriteByte('"')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render writes the attributes to the given as attributes to an HTML element.
|
||||||
|
// This is used for the default codeblock renderering.
|
||||||
|
// This performs HTML esacaping of string attributes.
|
||||||
|
func RenderAttributes(w hugio.FlexiWriter, skipClass bool, attributes ...Attribute) {
|
||||||
|
for _, attr := range attributes {
|
||||||
|
a := strings.ToLower(string(attr.Name))
|
||||||
|
if skipClass && a == "class" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, _ = w.WriteString(" ")
|
||||||
|
_, _ = w.WriteString(attr.Name)
|
||||||
|
_, _ = w.WriteString(`="`)
|
||||||
|
|
||||||
|
switch v := attr.Value.(type) {
|
||||||
|
case []byte:
|
||||||
|
_, _ = w.Write(util.EscapeHTML(v))
|
||||||
|
default:
|
||||||
|
w.WriteString(cast.ToString(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = w.WriteByte('"')
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,11 +39,8 @@ func NewConverterProvider(cfg converter.ProviderConfig) (ConverterProvider, erro
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Highlight == nil {
|
if cfg.Highlighter == nil {
|
||||||
h := highlight.New(markupConfig.Highlight)
|
cfg.Highlighter = highlight.New(markupConfig.Highlight)
|
||||||
cfg.Highlight = func(code, lang, optsStr string) (string, error) {
|
|
||||||
return h.Highlight(code, lang, optsStr)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.MarkupConfig = markupConfig
|
cfg.MarkupConfig = markupConfig
|
||||||
|
@ -95,7 +92,7 @@ type ConverterProvider interface {
|
||||||
Get(name string) converter.Provider
|
Get(name string) converter.Provider
|
||||||
// Default() converter.Provider
|
// Default() converter.Provider
|
||||||
GetMarkupConfig() markup_config.Config
|
GetMarkupConfig() markup_config.Config
|
||||||
Highlight(code, lang, optsStr string) (string, error)
|
GetHighlighter() highlight.Highlighter
|
||||||
}
|
}
|
||||||
|
|
||||||
type converterRegistry struct {
|
type converterRegistry struct {
|
||||||
|
@ -112,8 +109,8 @@ func (r *converterRegistry) Get(name string) converter.Provider {
|
||||||
return r.converters[strings.ToLower(name)]
|
return r.converters[strings.ToLower(name)]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *converterRegistry) Highlight(code, lang, optsStr string) (string, error) {
|
func (r *converterRegistry) GetHighlighter() highlight.Highlighter {
|
||||||
return r.config.Highlight(code, lang, optsStr)
|
return r.config.Highlighter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *converterRegistry) GetMarkupConfig() markup_config.Config {
|
func (r *converterRegistry) GetMarkupConfig() markup_config.Config {
|
||||||
|
|
|
@ -27,8 +27,7 @@ import (
|
||||||
// Provider is the package entry point.
|
// Provider is the package entry point.
|
||||||
var Provider converter.ProviderProvider = provide{}
|
var Provider converter.ProviderProvider = provide{}
|
||||||
|
|
||||||
type provide struct {
|
type provide struct{}
|
||||||
}
|
|
||||||
|
|
||||||
func (p provide) New(cfg converter.ProviderConfig) (converter.Provider, error) {
|
func (p provide) New(cfg converter.ProviderConfig) (converter.Provider, error) {
|
||||||
return converter.NewProvider("org", func(ctx converter.DocumentContext) (converter.Converter, error) {
|
return converter.NewProvider("org", func(ctx converter.DocumentContext) (converter.Converter, error) {
|
||||||
|
|
|
@ -31,7 +31,13 @@ var reservedSections = map[string]bool{
|
||||||
type LayoutDescriptor struct {
|
type LayoutDescriptor struct {
|
||||||
Type string
|
Type string
|
||||||
Section string
|
Section string
|
||||||
|
|
||||||
|
// E.g. "page", but also used for the _markup render kinds, e.g. "render-image".
|
||||||
Kind string
|
Kind string
|
||||||
|
|
||||||
|
// Comma-separated list of kind variants, e.g. "go,json" as variants which would find "render-codeblock-go.html"
|
||||||
|
KindVariants string
|
||||||
|
|
||||||
Lang string
|
Lang string
|
||||||
Layout string
|
Layout string
|
||||||
// LayoutOverride indicates what we should only look for the above layout.
|
// LayoutOverride indicates what we should only look for the above layout.
|
||||||
|
@ -139,6 +145,12 @@ func resolvePageTemplate(d LayoutDescriptor, f Format) []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.RenderingHook {
|
if d.RenderingHook {
|
||||||
|
if d.KindVariants != "" {
|
||||||
|
// Add the more specific variants first.
|
||||||
|
for _, variant := range strings.Split(d.KindVariants, ",") {
|
||||||
|
b.addLayoutVariations(d.Kind + "-" + variant)
|
||||||
|
}
|
||||||
|
}
|
||||||
b.addLayoutVariations(d.Kind)
|
b.addLayoutVariations(d.Kind)
|
||||||
b.addSectionType()
|
b.addSectionType()
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ type Site interface {
|
||||||
Language() *langs.Language
|
Language() *langs.Language
|
||||||
RegularPages() Pages
|
RegularPages() Pages
|
||||||
Pages() Pages
|
Pages() Pages
|
||||||
|
Home() Page
|
||||||
IsServer() bool
|
IsServer() bool
|
||||||
ServerPort() int
|
ServerPort() int
|
||||||
Title() string
|
Title() string
|
||||||
|
@ -89,6 +90,10 @@ func (t testSite) Language() *langs.Language {
|
||||||
return t.l
|
return t.l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t testSite) Home() Page {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (t testSite) Pages() Pages {
|
func (t testSite) Pages() Pages {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
// Copyright 2017 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 cast
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(&deps.Deps{})
|
|
||||||
if ns.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, &Namespace{})
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
// Copyright 2017 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 collections
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(&deps.Deps{})
|
|
||||||
if ns.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, &Namespace{})
|
|
||||||
}
|
|
|
@ -40,14 +40,14 @@ func init() {
|
||||||
ns.AddMethodMapping(ctx.Eq,
|
ns.AddMethodMapping(ctx.Eq,
|
||||||
[]string{"eq"},
|
[]string{"eq"},
|
||||||
[][2]string{
|
[][2]string{
|
||||||
{`{{ if eq .Section "blog" }}current{{ end }}`, `current`},
|
{`{{ if eq .Section "blog" }}current-section{{ end }}`, `current-section`},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
ns.AddMethodMapping(ctx.Ge,
|
ns.AddMethodMapping(ctx.Ge,
|
||||||
[]string{"ge"},
|
[]string{"ge"},
|
||||||
[][2]string{
|
[][2]string{
|
||||||
{`{{ if ge .Hugo.Version "0.36" }}Reasonable new Hugo version!{{ end }}`, `Reasonable new Hugo version!`},
|
{`{{ if ge hugo.Version "0.80" }}Reasonable new Hugo version!{{ end }}`, `Reasonable new Hugo version!`},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
// Copyright 2017 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 compare
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(&deps.Deps{})
|
|
||||||
if ns.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, &Namespace{})
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
// Copyright 2017 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 crypto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(&deps.Deps{})
|
|
||||||
if ns.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, &Namespace{})
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
// Copyright 2017 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 data
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/config"
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
"github.com/gohugoio/hugo/langs"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
|
|
||||||
v := config.New()
|
|
||||||
v.Set("contentDir", "content")
|
|
||||||
langs.LoadLanguageSettings(v, nil)
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(newDeps(v))
|
|
||||||
if ns.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, &Namespace{})
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
// Copyright 2020 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 debug
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/common/loggers"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(&deps.Deps{Log: loggers.NewErrorLogger()})
|
|
||||||
if ns.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, &Namespace{})
|
|
||||||
}
|
|
73
tpl/diagrams/diagrams.go
Normal file
73
tpl/diagrams/diagrams.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
// 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 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 diagrams
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bep/goat"
|
||||||
|
"github.com/gohugoio/hugo/deps"
|
||||||
|
"github.com/spf13/cast"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SVGDiagram interface {
|
||||||
|
Body() template.HTML
|
||||||
|
SVG() template.HTML
|
||||||
|
Width() int
|
||||||
|
Height() int
|
||||||
|
}
|
||||||
|
|
||||||
|
type goatDiagram struct {
|
||||||
|
d goat.SVG
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d goatDiagram) Body() template.HTML {
|
||||||
|
return template.HTML(d.d.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d goatDiagram) SVG() template.HTML {
|
||||||
|
return template.HTML(d.d.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d goatDiagram) Width() int {
|
||||||
|
return d.d.Width
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d goatDiagram) Height() int {
|
||||||
|
return d.d.Height
|
||||||
|
}
|
||||||
|
|
||||||
|
type Diagrams struct {
|
||||||
|
d *deps.Deps
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Diagrams) Goat(v interface{}) SVGDiagram {
|
||||||
|
var r io.Reader
|
||||||
|
|
||||||
|
switch vv := v.(type) {
|
||||||
|
case io.Reader:
|
||||||
|
r = vv
|
||||||
|
case []byte:
|
||||||
|
r = bytes.NewReader(vv)
|
||||||
|
default:
|
||||||
|
r = strings.NewReader(cast.ToString(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
return goatDiagram{
|
||||||
|
d: goat.BuildSVG(r),
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2017 The Hugo Authors. All rights reserved.
|
// Copyright 2022 The Hugo Authors. All rights reserved.
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -11,32 +11,28 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package os
|
package diagrams
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
"github.com/gohugoio/hugo/deps"
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
"github.com/gohugoio/hugo/tpl/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
const name = "diagrams"
|
||||||
c := qt.New(t)
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
func init() {
|
||||||
ns = nsf(&deps.Deps{})
|
f := func(d *deps.Deps) *internal.TemplateFuncsNamespace {
|
||||||
if ns.Name == name {
|
ctx := &Diagrams{
|
||||||
found = true
|
d: d,
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
ns := &internal.TemplateFuncsNamespace{
|
||||||
ctx, err := ns.Context()
|
Name: name,
|
||||||
c.Assert(err, qt.IsNil)
|
Context: func(args ...interface{}) (interface{}, error) { return ctx, nil },
|
||||||
c.Assert(ctx, hqt.IsSameType, &Namespace{})
|
}
|
||||||
|
|
||||||
|
return ns
|
||||||
|
}
|
||||||
|
|
||||||
|
internal.AddTemplateFuncsNamespace(f)
|
||||||
}
|
}
|
|
@ -1,42 +0,0 @@
|
||||||
// Copyright 2017 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 encoding
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(&deps.Deps{})
|
|
||||||
if ns.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, &Namespace{})
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
// Copyright 2017 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 fmt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/common/loggers"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(&deps.Deps{Log: loggers.NewIgnorableLogger(loggers.NewErrorLogger())})
|
|
||||||
if ns.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, &Namespace{})
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
// Copyright 2017 The Hugo Authors. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package hugo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/config"
|
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/resources/page"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
v := config.New()
|
|
||||||
v.Set("contentDir", "content")
|
|
||||||
s := page.NewDummyHugoSite(v)
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(&deps.Deps{Site: s})
|
|
||||||
if ns.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, s.Hugo())
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
// Copyright 2017 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 images
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(&deps.Deps{})
|
|
||||||
if ns.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, &Namespace{})
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
// Copyright 2017 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 inflect
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(&deps.Deps{})
|
|
||||||
if ns.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, &Namespace{})
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
// Copyright 2017 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 lang
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/config"
|
|
||||||
"github.com/gohugoio/hugo/langs"
|
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(&deps.Deps{
|
|
||||||
Language: langs.NewDefaultLanguage(config.New()),
|
|
||||||
})
|
|
||||||
if ns.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, &Namespace{})
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
// Copyright 2017 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 math
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(&deps.Deps{})
|
|
||||||
if ns.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, &Namespace{})
|
|
||||||
}
|
|
21
tpl/os/os.go
21
tpl/os/os.go
|
@ -19,6 +19,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
_os "os"
|
_os "os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
"github.com/gohugoio/hugo/deps"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
|
@ -27,17 +28,9 @@ import (
|
||||||
|
|
||||||
// New returns a new instance of the os-namespaced template functions.
|
// New returns a new instance of the os-namespaced template functions.
|
||||||
func New(d *deps.Deps) *Namespace {
|
func New(d *deps.Deps) *Namespace {
|
||||||
var rfs afero.Fs
|
|
||||||
if d.Fs != nil {
|
|
||||||
rfs = d.Fs.WorkingDir
|
|
||||||
if d.PathSpec != nil && d.PathSpec.BaseFs != nil {
|
|
||||||
rfs = afero.NewReadOnlyFs(afero.NewCopyOnWriteFs(d.PathSpec.BaseFs.Content.Fs, d.Fs.WorkingDir))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Namespace{
|
return &Namespace{
|
||||||
readFileFs: rfs,
|
readFileFs: afero.NewReadOnlyFs(afero.NewCopyOnWriteFs(d.PathSpec.BaseFs.Content.Fs, d.PathSpec.BaseFs.Work)),
|
||||||
|
workFs: d.PathSpec.BaseFs.Work,
|
||||||
deps: d,
|
deps: d,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,6 +38,7 @@ func New(d *deps.Deps) *Namespace {
|
||||||
// Namespace provides template functions for the "os" namespace.
|
// Namespace provides template functions for the "os" namespace.
|
||||||
type Namespace struct {
|
type Namespace struct {
|
||||||
readFileFs afero.Fs
|
readFileFs afero.Fs
|
||||||
|
workFs afero.Fs
|
||||||
deps *deps.Deps
|
deps *deps.Deps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,8 +60,9 @@ func (ns *Namespace) Getenv(key interface{}) (string, error) {
|
||||||
// readFile reads the file named by filename in the given filesystem
|
// readFile reads the file named by filename in the given filesystem
|
||||||
// and returns the contents as a string.
|
// and returns the contents as a string.
|
||||||
func readFile(fs afero.Fs, filename string) (string, error) {
|
func readFile(fs afero.Fs, filename string) (string, error) {
|
||||||
if filename == "" {
|
filename = filepath.Clean(filename)
|
||||||
return "", errors.New("readFile needs a filename")
|
if filename == "" || filename == "." || filename == string(_os.PathSeparator) {
|
||||||
|
return "", errors.New("invalid filename")
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := afero.ReadFile(fs, filename)
|
b, err := afero.ReadFile(fs, filename)
|
||||||
|
@ -101,7 +96,7 @@ func (ns *Namespace) ReadDir(i interface{}) ([]_os.FileInfo, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
list, err := afero.ReadDir(ns.deps.Fs.WorkingDir, path)
|
list, err := afero.ReadDir(ns.workFs, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read directory %q: %s", path, err)
|
return nil, fmt.Errorf("failed to read directory %q: %s", path, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,34 +11,26 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package os
|
package os_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/config"
|
"github.com/gohugoio/hugo/hugolib"
|
||||||
|
"github.com/gohugoio/hugo/tpl/os"
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
qt "github.com/frankban/quicktest"
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/hugofs"
|
|
||||||
"github.com/spf13/afero"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestReadFile(t *testing.T) {
|
func TestReadFile(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
c := qt.New(t)
|
|
||||||
|
|
||||||
workingDir := "/home/hugo"
|
b := newFileTestBuilder(t).Build()
|
||||||
|
|
||||||
v := config.New()
|
// helpers.PrintFs(b.H.PathSpec.BaseFs.Work, "", _os.Stdout)
|
||||||
v.Set("workingDir", workingDir)
|
|
||||||
|
|
||||||
// f := newTestFuncsterWithViper(v)
|
ns := os.New(b.H.Deps)
|
||||||
ns := New(&deps.Deps{Fs: hugofs.NewMem(v)})
|
|
||||||
|
|
||||||
afero.WriteFile(ns.deps.Fs.Source, filepath.Join(workingDir, "/f/f1.txt"), []byte("f1-content"), 0755)
|
|
||||||
afero.WriteFile(ns.deps.Fs.Source, filepath.Join("/home", "f2.txt"), []byte("f2-content"), 0755)
|
|
||||||
|
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
filename string
|
filename string
|
||||||
|
@ -53,13 +45,13 @@ func TestReadFile(t *testing.T) {
|
||||||
|
|
||||||
result, err := ns.ReadFile(test.filename)
|
result, err := ns.ReadFile(test.filename)
|
||||||
|
|
||||||
if b, ok := test.expect.(bool); ok && !b {
|
if bb, ok := test.expect.(bool); ok && !bb {
|
||||||
c.Assert(err, qt.Not(qt.IsNil))
|
b.Assert(err, qt.Not(qt.IsNil))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Assert(err, qt.IsNil)
|
b.Assert(err, qt.IsNil)
|
||||||
c.Assert(result, qt.Equals, test.expect)
|
b.Assert(result, qt.Equals, test.expect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,15 +59,8 @@ func TestFileExists(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
c := qt.New(t)
|
c := qt.New(t)
|
||||||
|
|
||||||
workingDir := "/home/hugo"
|
b := newFileTestBuilder(t).Build()
|
||||||
|
ns := os.New(b.H.Deps)
|
||||||
v := config.New()
|
|
||||||
v.Set("workingDir", workingDir)
|
|
||||||
|
|
||||||
ns := New(&deps.Deps{Fs: hugofs.NewMem(v)})
|
|
||||||
|
|
||||||
afero.WriteFile(ns.deps.Fs.Source, filepath.Join(workingDir, "/f/f1.txt"), []byte("f1-content"), 0755)
|
|
||||||
afero.WriteFile(ns.deps.Fs.Source, filepath.Join("/home", "f2.txt"), []byte("f2-content"), 0755)
|
|
||||||
|
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
filename string
|
filename string
|
||||||
|
@ -101,15 +86,8 @@ func TestFileExists(t *testing.T) {
|
||||||
|
|
||||||
func TestStat(t *testing.T) {
|
func TestStat(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
c := qt.New(t)
|
b := newFileTestBuilder(t).Build()
|
||||||
workingDir := "/home/hugo"
|
ns := os.New(b.H.Deps)
|
||||||
|
|
||||||
v := config.New()
|
|
||||||
v.Set("workingDir", workingDir)
|
|
||||||
|
|
||||||
ns := New(&deps.Deps{Fs: hugofs.NewMem(v)})
|
|
||||||
|
|
||||||
afero.WriteFile(ns.deps.Fs.Source, filepath.Join(workingDir, "/f/f1.txt"), []byte("f1-content"), 0755)
|
|
||||||
|
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
filename string
|
filename string
|
||||||
|
@ -123,11 +101,28 @@ func TestStat(t *testing.T) {
|
||||||
result, err := ns.Stat(test.filename)
|
result, err := ns.Stat(test.filename)
|
||||||
|
|
||||||
if test.expect == nil {
|
if test.expect == nil {
|
||||||
c.Assert(err, qt.Not(qt.IsNil))
|
b.Assert(err, qt.Not(qt.IsNil))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Assert(err, qt.IsNil)
|
b.Assert(err, qt.IsNil)
|
||||||
c.Assert(result.Size(), qt.Equals, test.expect)
|
b.Assert(result.Size(), qt.Equals, test.expect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newFileTestBuilder(t *testing.T) *hugolib.IntegrationTestBuilder {
|
||||||
|
files := `
|
||||||
|
-- f/f1.txt --
|
||||||
|
f1-content
|
||||||
|
-- home/f2.txt --
|
||||||
|
f2-content
|
||||||
|
`
|
||||||
|
|
||||||
|
return hugolib.NewIntegrationTestBuilder(
|
||||||
|
hugolib.IntegrationTestConfig{
|
||||||
|
T: t,
|
||||||
|
TxtarString: files,
|
||||||
|
WorkingDir: "/mywork",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
// Copyright 2017 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 partials
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/common/loggers"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(&deps.Deps{
|
|
||||||
BuildStartListeners: &deps.Listeners{},
|
|
||||||
Log: loggers.NewErrorLogger(),
|
|
||||||
})
|
|
||||||
if ns.Name == namespaceName {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, &Namespace{})
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
// Copyright 2018 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 path
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(&deps.Deps{})
|
|
||||||
if ns.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, &Namespace{})
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
// Copyright 2017 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 reflect
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/common/loggers"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(&deps.Deps{Log: loggers.NewErrorLogger()})
|
|
||||||
if ns.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, &Namespace{})
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
// Copyright 2017 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 safe
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(&deps.Deps{})
|
|
||||||
if ns.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, &Namespace{})
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
// Copyright 2017 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 site
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/config"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
"github.com/gohugoio/hugo/resources/page"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
v := config.New()
|
|
||||||
v.Set("contentDir", "content")
|
|
||||||
s := page.NewDummyHugoSite(v)
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(&deps.Deps{Site: s})
|
|
||||||
if ns.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, s)
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
// Copyright 2017 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 strings
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/config"
|
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(&deps.Deps{Cfg: config.New()})
|
|
||||||
if ns.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, &Namespace{})
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
// Copyright 2018 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 templates
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(&deps.Deps{})
|
|
||||||
if ns.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, &Namespace{})
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
// Copyright 2017 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 time
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/config"
|
|
||||||
"github.com/gohugoio/hugo/langs"
|
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(&deps.Deps{
|
|
||||||
Language: langs.NewDefaultLanguage(config.New()),
|
|
||||||
})
|
|
||||||
if ns.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, &Namespace{})
|
|
||||||
}
|
|
|
@ -0,0 +1 @@
|
||||||
|
adf
|
|
@ -281,15 +281,10 @@ func (t *templateExec) UnusedTemplates() []tpl.FileInfo {
|
||||||
|
|
||||||
for _, ts := range t.main.templates {
|
for _, ts := range t.main.templates {
|
||||||
ti := ts.info
|
ti := ts.info
|
||||||
if strings.HasPrefix(ti.name, "_internal/") {
|
if strings.HasPrefix(ti.name, "_internal/") || ti.realFilename == "" {
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(ti.name, "partials/inline/pagination") {
|
|
||||||
// TODO(bep) we need to fix this. These are internal partials, but
|
|
||||||
// they may also be defined in the project, which currently could
|
|
||||||
// lead to some false negatives.
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, found := t.templateUsageTracker[ti.name]; !found {
|
if _, found := t.templateUsageTracker[ti.name]; !found {
|
||||||
unused = append(unused, ti)
|
unused = append(unused, ti)
|
||||||
}
|
}
|
||||||
|
@ -740,6 +735,7 @@ func (t *templateHandler) extractIdentifiers(line string) []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed embedded/templates/*
|
//go:embed embedded/templates/*
|
||||||
|
//go:embed embedded/templates/_default/*
|
||||||
var embededTemplatesFs embed.FS
|
var embededTemplatesFs embed.FS
|
||||||
|
|
||||||
func (t *templateHandler) loadEmbedded() error {
|
func (t *templateHandler) loadEmbedded() error {
|
||||||
|
@ -757,10 +753,20 @@ func (t *templateHandler) loadEmbedded() error {
|
||||||
// to write the templates to Go files.
|
// to write the templates to Go files.
|
||||||
templ := string(bytes.ReplaceAll(templb, []byte("\r\n"), []byte("\n")))
|
templ := string(bytes.ReplaceAll(templb, []byte("\r\n"), []byte("\n")))
|
||||||
name := strings.TrimPrefix(filepath.ToSlash(path), "embedded/templates/")
|
name := strings.TrimPrefix(filepath.ToSlash(path), "embedded/templates/")
|
||||||
|
templateName := name
|
||||||
|
|
||||||
if err := t.AddTemplate(internalPathPrefix+name, templ); err != nil {
|
// For the render hooks it does not make sense to preseve the
|
||||||
|
// double _indternal double book-keeping,
|
||||||
|
// just add it if its now provided by the user.
|
||||||
|
if !strings.Contains(path, "_default/_markup") {
|
||||||
|
templateName = internalPathPrefix + name
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, found := t.Lookup(templateName); !found {
|
||||||
|
if err := t.AddTemplate(templateName, templ); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if aliases, found := embeddedTemplatesAliases[name]; found {
|
if aliases, found := embeddedTemplatesAliases[name]; found {
|
||||||
// TODO(bep) avoid reparsing these aliases
|
// TODO(bep) avoid reparsing these aliases
|
||||||
|
|
|
@ -38,6 +38,7 @@ import (
|
||||||
_ "github.com/gohugoio/hugo/tpl/crypto"
|
_ "github.com/gohugoio/hugo/tpl/crypto"
|
||||||
_ "github.com/gohugoio/hugo/tpl/data"
|
_ "github.com/gohugoio/hugo/tpl/data"
|
||||||
_ "github.com/gohugoio/hugo/tpl/debug"
|
_ "github.com/gohugoio/hugo/tpl/debug"
|
||||||
|
_ "github.com/gohugoio/hugo/tpl/diagrams"
|
||||||
_ "github.com/gohugoio/hugo/tpl/encoding"
|
_ "github.com/gohugoio/hugo/tpl/encoding"
|
||||||
_ "github.com/gohugoio/hugo/tpl/fmt"
|
_ "github.com/gohugoio/hugo/tpl/fmt"
|
||||||
_ "github.com/gohugoio/hugo/tpl/hugo"
|
_ "github.com/gohugoio/hugo/tpl/hugo"
|
||||||
|
|
|
@ -11,223 +11,74 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package tplimpl
|
package tplimpl_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"strings"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/modules"
|
"github.com/gohugoio/hugo/hugolib"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/resources/page"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/common/hugo"
|
|
||||||
"github.com/gohugoio/hugo/common/loggers"
|
|
||||||
"github.com/gohugoio/hugo/config"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/hugofs"
|
|
||||||
"github.com/gohugoio/hugo/langs"
|
|
||||||
"github.com/gohugoio/hugo/langs/i18n"
|
|
||||||
"github.com/gohugoio/hugo/tpl"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
"github.com/gohugoio/hugo/tpl/internal"
|
||||||
"github.com/gohugoio/hugo/tpl/partials"
|
|
||||||
"github.com/spf13/afero"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var logger = loggers.NewErrorLogger()
|
|
||||||
|
|
||||||
func newTestConfig() config.Provider {
|
|
||||||
v := config.New()
|
|
||||||
v.Set("contentDir", "content")
|
|
||||||
v.Set("dataDir", "data")
|
|
||||||
v.Set("i18nDir", "i18n")
|
|
||||||
v.Set("layoutDir", "layouts")
|
|
||||||
v.Set("archetypeDir", "archetypes")
|
|
||||||
v.Set("assetDir", "assets")
|
|
||||||
v.Set("resourceDir", "resources")
|
|
||||||
v.Set("publishDir", "public")
|
|
||||||
|
|
||||||
langs.LoadLanguageSettings(v, nil)
|
|
||||||
mod, err := modules.CreateProjectModule(v)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
v.Set("allModules", modules.Modules{mod})
|
|
||||||
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
func newDepsConfig(cfg config.Provider) deps.DepsCfg {
|
|
||||||
l := langs.NewLanguage("en", cfg)
|
|
||||||
return deps.DepsCfg{
|
|
||||||
Language: l,
|
|
||||||
Site: page.NewDummyHugoSite(cfg),
|
|
||||||
Cfg: cfg,
|
|
||||||
Fs: hugofs.NewMem(l),
|
|
||||||
Logger: logger,
|
|
||||||
TemplateProvider: DefaultTemplateProvider,
|
|
||||||
TranslationProvider: i18n.NewTranslationProvider(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTemplateFuncsExamples(t *testing.T) {
|
func TestTemplateFuncsExamples(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
c := qt.New(t)
|
|
||||||
|
|
||||||
workingDir := "/home/hugo"
|
files := `
|
||||||
|
-- config.toml --
|
||||||
|
disableKinds=["home", "section", "taxonomy", "term", "sitemap", "robotsTXT"]
|
||||||
|
ignoreErrors = ["my-err-id"]
|
||||||
|
[outputs]
|
||||||
|
home=["HTML"]
|
||||||
|
-- layouts/partials/header.html --
|
||||||
|
<title>Hugo Rocks!</title>
|
||||||
|
-- files/README.txt --
|
||||||
|
Hugo Rocks!
|
||||||
|
-- content/blog/hugo-rocks.md --
|
||||||
|
---
|
||||||
|
title: "**BatMan**"
|
||||||
|
---
|
||||||
|
`
|
||||||
|
|
||||||
v := newTestConfig()
|
b := hugolib.NewIntegrationTestBuilder(
|
||||||
|
hugolib.IntegrationTestConfig{
|
||||||
|
T: t,
|
||||||
|
TxtarString: files,
|
||||||
|
NeedsOsFS: true,
|
||||||
|
},
|
||||||
|
).Build()
|
||||||
|
|
||||||
v.Set("workingDir", workingDir)
|
d := b.H.Sites[0].Deps
|
||||||
v.Set("multilingual", true)
|
|
||||||
v.Set("contentDir", "content")
|
|
||||||
v.Set("assetDir", "assets")
|
|
||||||
v.Set("baseURL", "http://mysite.com/hugo/")
|
|
||||||
v.Set("CurrentContentLanguage", langs.NewLanguage("en", v))
|
|
||||||
|
|
||||||
fs := hugofs.NewMem(v)
|
var (
|
||||||
|
templates []string
|
||||||
afero.WriteFile(fs.Source, filepath.Join(workingDir, "files", "README.txt"), []byte("Hugo Rocks!"), 0755)
|
expected []string
|
||||||
|
)
|
||||||
depsCfg := newDepsConfig(v)
|
|
||||||
depsCfg.Fs = fs
|
|
||||||
d, err := deps.New(depsCfg)
|
|
||||||
defer d.Close()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
|
|
||||||
var data struct {
|
|
||||||
Title string
|
|
||||||
Section string
|
|
||||||
Hugo map[string]interface{}
|
|
||||||
Params map[string]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
data.Title = "**BatMan**"
|
|
||||||
data.Section = "blog"
|
|
||||||
data.Params = map[string]interface{}{"langCode": "en"}
|
|
||||||
data.Hugo = map[string]interface{}{"Version": hugo.MustParseVersion("0.36.1").Version()}
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
||||||
ns := nsf(d)
|
ns := nsf(d)
|
||||||
for _, mm := range ns.MethodMappings {
|
for _, mm := range ns.MethodMappings {
|
||||||
for i, example := range mm.Examples {
|
for _, example := range mm.Examples {
|
||||||
in, expected := example[0], example[1]
|
if strings.Contains(example[0], "errorf") {
|
||||||
d.WithTemplate = func(templ tpl.TemplateManager) error {
|
// This will fail the build, so skip for now.
|
||||||
c.Assert(templ.AddTemplate("test", in), qt.IsNil)
|
continue
|
||||||
c.Assert(templ.AddTemplate("partials/header.html", "<title>Hugo Rocks!</title>"), qt.IsNil)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
c.Assert(d.LoadResources(), qt.IsNil)
|
templates = append(templates, example[0])
|
||||||
|
expected = append(expected, example[1])
|
||||||
var b bytes.Buffer
|
|
||||||
templ, _ := d.Tmpl().Lookup("test")
|
|
||||||
c.Assert(d.Tmpl().Execute(templ, &b, &data), qt.IsNil)
|
|
||||||
if b.String() != expected {
|
|
||||||
t.Fatalf("%s[%d]: got %q expected %q", ns.Name, i, b.String(), expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(bep) it would be dandy to put this one into the partials package, but
|
|
||||||
// we have some package cycle issues to solve first.
|
|
||||||
func TestPartialCached(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
c := qt.New(t)
|
|
||||||
|
|
||||||
partial := `Now: {{ now.UnixNano }}`
|
|
||||||
name := "testing"
|
|
||||||
|
|
||||||
var data struct{}
|
|
||||||
|
|
||||||
v := newTestConfig()
|
|
||||||
|
|
||||||
config := newDepsConfig(v)
|
|
||||||
|
|
||||||
config.WithTemplate = func(templ tpl.TemplateManager) error {
|
|
||||||
err := templ.AddTemplate("partials/"+name, partial)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
de, err := deps.New(config)
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
defer de.Close()
|
|
||||||
c.Assert(de.LoadResources(), qt.IsNil)
|
|
||||||
|
|
||||||
ns := partials.New(de)
|
|
||||||
|
|
||||||
res1, err := ns.IncludeCached(context.Background(), name, &data)
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
|
|
||||||
for j := 0; j < 10; j++ {
|
|
||||||
time.Sleep(2 * time.Nanosecond)
|
|
||||||
res2, err := ns.IncludeCached(context.Background(), name, &data)
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(res1, res2) {
|
|
||||||
t.Fatalf("cache mismatch")
|
|
||||||
}
|
|
||||||
|
|
||||||
res3, err := ns.IncludeCached(context.Background(), name, &data, fmt.Sprintf("variant%d", j))
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
|
|
||||||
if reflect.DeepEqual(res1, res3) {
|
|
||||||
t.Fatalf("cache mismatch")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkPartial(b *testing.B) {
|
files += fmt.Sprintf("-- layouts/_default/single.html --\n%s\n", strings.Join(templates, "\n"))
|
||||||
doBenchmarkPartial(b, func(ns *partials.Namespace) error {
|
b = hugolib.NewIntegrationTestBuilder(
|
||||||
_, err := ns.Include(context.Background(), "bench1")
|
hugolib.IntegrationTestConfig{
|
||||||
return err
|
T: t,
|
||||||
})
|
TxtarString: files,
|
||||||
}
|
NeedsOsFS: true,
|
||||||
|
},
|
||||||
|
).Build()
|
||||||
|
|
||||||
func BenchmarkPartialCached(b *testing.B) {
|
b.AssertFileContent("public/blog/hugo-rocks/index.html", expected...)
|
||||||
doBenchmarkPartial(b, func(ns *partials.Namespace) error {
|
|
||||||
_, err := ns.IncludeCached(context.Background(), "bench1", nil)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func doBenchmarkPartial(b *testing.B, f func(ns *partials.Namespace) error) {
|
|
||||||
c := qt.New(b)
|
|
||||||
config := newDepsConfig(config.New())
|
|
||||||
config.WithTemplate = func(templ tpl.TemplateManager) error {
|
|
||||||
err := templ.AddTemplate("partials/bench1", `{{ shuffle (seq 1 10) }}`)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
de, err := deps.New(config)
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
defer de.Close()
|
|
||||||
c.Assert(de.LoadResources(), qt.IsNil)
|
|
||||||
|
|
||||||
ns := partials.New(de)
|
|
||||||
|
|
||||||
b.ResetTimer()
|
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
|
||||||
for pb.Next() {
|
|
||||||
if err := f(ns); err != nil {
|
|
||||||
b.Fatalf("error executing template: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
// Copyright 2019 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 tplimpl
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/hugofs"
|
|
||||||
"github.com/gohugoio/hugo/tpl"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTemplateInfoShortcode(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
d := newD(c)
|
|
||||||
defer d.Close()
|
|
||||||
h := d.Tmpl().(*templateExec)
|
|
||||||
|
|
||||||
c.Assert(h.AddTemplate("shortcodes/mytemplate.html", `
|
|
||||||
{{ .Inner }}
|
|
||||||
`), qt.IsNil)
|
|
||||||
|
|
||||||
c.Assert(h.postTransform(), qt.IsNil)
|
|
||||||
|
|
||||||
tt, found, _ := d.Tmpl().LookupVariant("mytemplate", tpl.TemplateVariants{})
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
tti, ok := tt.(tpl.Info)
|
|
||||||
c.Assert(ok, qt.Equals, true)
|
|
||||||
c.Assert(tti.ParseInfo().IsInner, qt.Equals, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(bep) move and use in other places
|
|
||||||
func newD(c *qt.C) *deps.Deps {
|
|
||||||
v := newTestConfig()
|
|
||||||
fs := hugofs.NewMem(v)
|
|
||||||
|
|
||||||
depsCfg := newDepsConfig(v)
|
|
||||||
depsCfg.Fs = fs
|
|
||||||
d, err := deps.New(depsCfg)
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
|
|
||||||
provider := DefaultTemplateProvider
|
|
||||||
provider.Update(d)
|
|
||||||
|
|
||||||
return d
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
// Copyright 2017 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 transform
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(&deps.Deps{})
|
|
||||||
if ns.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, &Namespace{})
|
|
||||||
}
|
|
|
@ -11,13 +11,14 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package transform
|
package transform_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/config"
|
|
||||||
"github.com/gohugoio/hugo/htesting"
|
"github.com/gohugoio/hugo/htesting"
|
||||||
|
"github.com/gohugoio/hugo/hugolib"
|
||||||
|
"github.com/gohugoio/hugo/tpl/transform"
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
qt "github.com/frankban/quicktest"
|
||||||
)
|
)
|
||||||
|
@ -25,13 +26,14 @@ import (
|
||||||
func TestRemarshal(t *testing.T) {
|
func TestRemarshal(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
v := config.New()
|
b := hugolib.NewIntegrationTestBuilder(
|
||||||
v.Set("contentDir", "content")
|
hugolib.IntegrationTestConfig{T: t},
|
||||||
ns := New(newDeps(v))
|
).Build()
|
||||||
|
|
||||||
|
ns := transform.New(b.H.Deps)
|
||||||
c := qt.New(t)
|
c := qt.New(t)
|
||||||
|
|
||||||
c.Run("Roundtrip variants", func(c *qt.C) {
|
c.Run("Roundtrip variants", func(c *qt.C) {
|
||||||
|
|
||||||
tomlExample := `title = 'Test Metadata'
|
tomlExample := `title = 'Test Metadata'
|
||||||
|
|
||||||
[[resources]]
|
[[resources]]
|
||||||
|
@ -129,7 +131,6 @@ title: Test Metadata
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
c.Run("Comments", func(c *qt.C) {
|
c.Run("Comments", func(c *qt.C) {
|
||||||
|
|
|
@ -19,6 +19,9 @@ import (
|
||||||
"html/template"
|
"html/template"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/cache/namedmemcache"
|
"github.com/gohugoio/hugo/cache/namedmemcache"
|
||||||
|
"github.com/gohugoio/hugo/common/herrors"
|
||||||
|
"github.com/gohugoio/hugo/markup/converter/hooks"
|
||||||
|
"github.com/gohugoio/hugo/markup/highlight"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
"github.com/gohugoio/hugo/deps"
|
||||||
"github.com/gohugoio/hugo/helpers"
|
"github.com/gohugoio/hugo/helpers"
|
||||||
|
@ -65,18 +68,28 @@ func (ns *Namespace) Highlight(s interface{}, lang string, opts ...interface{})
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
sopts := ""
|
var optsv interface{}
|
||||||
if len(opts) > 0 {
|
if len(opts) > 0 {
|
||||||
sopts, err = cast.ToStringE(opts[0])
|
optsv = opts[0]
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
highlighted, _ := ns.deps.ContentSpec.Converters.Highlight(ss, lang, sopts)
|
hl := ns.deps.ContentSpec.Converters.GetHighlighter()
|
||||||
|
highlighted, _ := hl.Highlight(ss, lang, optsv)
|
||||||
return template.HTML(highlighted), nil
|
return template.HTML(highlighted), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HighlightCodeBlock highlights a code block on the form received in the codeblock render hooks.
|
||||||
|
func (ns *Namespace) HighlightCodeBlock(ctx hooks.CodeblockContext, opts ...interface{}) (highlight.HightlightResult, error) {
|
||||||
|
var optsv interface{}
|
||||||
|
if len(opts) > 0 {
|
||||||
|
optsv = opts[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
hl := ns.deps.ContentSpec.Converters.GetHighlighter()
|
||||||
|
|
||||||
|
return hl.HighlightCodeBlock(ctx, optsv)
|
||||||
|
}
|
||||||
|
|
||||||
// HTMLEscape returns a copy of s with reserved HTML characters escaped.
|
// HTMLEscape returns a copy of s with reserved HTML characters escaped.
|
||||||
func (ns *Namespace) HTMLEscape(s interface{}) (string, error) {
|
func (ns *Namespace) HTMLEscape(s interface{}) (string, error) {
|
||||||
ss, err := cast.ToStringE(s)
|
ss, err := cast.ToStringE(s)
|
||||||
|
@ -100,20 +113,22 @@ func (ns *Namespace) HTMLUnescape(s interface{}) (string, error) {
|
||||||
|
|
||||||
// Markdownify renders a given input from Markdown to HTML.
|
// Markdownify renders a given input from Markdown to HTML.
|
||||||
func (ns *Namespace) Markdownify(s interface{}) (template.HTML, error) {
|
func (ns *Namespace) Markdownify(s interface{}) (template.HTML, error) {
|
||||||
|
defer herrors.Recover()
|
||||||
ss, err := cast.ToStringE(s)
|
ss, err := cast.ToStringE(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := ns.deps.ContentSpec.RenderMarkdown([]byte(ss))
|
home := ns.deps.Site.Home()
|
||||||
if err != nil {
|
if home == nil {
|
||||||
return "", err
|
panic("home must not be nil")
|
||||||
}
|
}
|
||||||
|
sss, err := home.RenderString(ss)
|
||||||
|
|
||||||
// Strip if this is a short inline type of text.
|
// Strip if this is a short inline type of text.
|
||||||
b = ns.deps.ContentSpec.TrimShortHTML(b)
|
bb := ns.deps.ContentSpec.TrimShortHTML([]byte(sss))
|
||||||
|
|
||||||
return helpers.BytesToHTML(b), nil
|
return helpers.BytesToHTML(bb), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plainify returns a copy of s with all HTML tags removed.
|
// Plainify returns a copy of s with all HTML tags removed.
|
||||||
|
@ -125,3 +140,7 @@ func (ns *Namespace) Plainify(s interface{}) (string, error) {
|
||||||
|
|
||||||
return helpers.StripHTML(ss), nil
|
return helpers.StripHTML(ss), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ns *Namespace) Reset() {
|
||||||
|
ns.cache.Clear()
|
||||||
|
}
|
||||||
|
|
|
@ -11,13 +11,15 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package transform
|
package transform_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/loggers"
|
"github.com/gohugoio/hugo/common/loggers"
|
||||||
|
"github.com/gohugoio/hugo/hugolib"
|
||||||
|
"github.com/gohugoio/hugo/tpl/transform"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
qt "github.com/frankban/quicktest"
|
||||||
|
@ -32,10 +34,11 @@ type tstNoStringer struct{}
|
||||||
|
|
||||||
func TestEmojify(t *testing.T) {
|
func TestEmojify(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
c := qt.New(t)
|
b := hugolib.NewIntegrationTestBuilder(
|
||||||
|
hugolib.IntegrationTestConfig{T: t},
|
||||||
|
).Build()
|
||||||
|
|
||||||
v := config.New()
|
ns := transform.New(b.H.Deps)
|
||||||
ns := New(newDeps(v))
|
|
||||||
|
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
s interface{}
|
s interface{}
|
||||||
|
@ -49,23 +52,23 @@ func TestEmojify(t *testing.T) {
|
||||||
|
|
||||||
result, err := ns.Emojify(test.s)
|
result, err := ns.Emojify(test.s)
|
||||||
|
|
||||||
if b, ok := test.expect.(bool); ok && !b {
|
if bb, ok := test.expect.(bool); ok && !bb {
|
||||||
c.Assert(err, qt.Not(qt.IsNil))
|
b.Assert(err, qt.Not(qt.IsNil))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Assert(err, qt.IsNil)
|
b.Assert(err, qt.IsNil)
|
||||||
c.Assert(result, qt.Equals, test.expect)
|
b.Assert(result, qt.Equals, test.expect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHighlight(t *testing.T) {
|
func TestHighlight(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
c := qt.New(t)
|
b := hugolib.NewIntegrationTestBuilder(
|
||||||
|
hugolib.IntegrationTestConfig{T: t},
|
||||||
|
).Build()
|
||||||
|
|
||||||
v := config.New()
|
ns := transform.New(b.H.Deps)
|
||||||
v.Set("contentDir", "content")
|
|
||||||
ns := New(newDeps(v))
|
|
||||||
|
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
s interface{}
|
s interface{}
|
||||||
|
@ -82,23 +85,23 @@ func TestHighlight(t *testing.T) {
|
||||||
|
|
||||||
result, err := ns.Highlight(test.s, test.lang, test.opts)
|
result, err := ns.Highlight(test.s, test.lang, test.opts)
|
||||||
|
|
||||||
if b, ok := test.expect.(bool); ok && !b {
|
if bb, ok := test.expect.(bool); ok && !bb {
|
||||||
c.Assert(err, qt.Not(qt.IsNil))
|
b.Assert(err, qt.Not(qt.IsNil))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Assert(err, qt.IsNil)
|
b.Assert(err, qt.IsNil)
|
||||||
c.Assert(string(result), qt.Contains, test.expect.(string))
|
b.Assert(string(result), qt.Contains, test.expect.(string))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTMLEscape(t *testing.T) {
|
func TestHTMLEscape(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
c := qt.New(t)
|
b := hugolib.NewIntegrationTestBuilder(
|
||||||
|
hugolib.IntegrationTestConfig{T: t},
|
||||||
|
).Build()
|
||||||
|
|
||||||
v := config.New()
|
ns := transform.New(b.H.Deps)
|
||||||
v.Set("contentDir", "content")
|
|
||||||
ns := New(newDeps(v))
|
|
||||||
|
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
s interface{}
|
s interface{}
|
||||||
|
@ -112,23 +115,23 @@ func TestHTMLEscape(t *testing.T) {
|
||||||
|
|
||||||
result, err := ns.HTMLEscape(test.s)
|
result, err := ns.HTMLEscape(test.s)
|
||||||
|
|
||||||
if b, ok := test.expect.(bool); ok && !b {
|
if bb, ok := test.expect.(bool); ok && !bb {
|
||||||
c.Assert(err, qt.Not(qt.IsNil))
|
b.Assert(err, qt.Not(qt.IsNil))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Assert(err, qt.IsNil)
|
b.Assert(err, qt.IsNil)
|
||||||
c.Assert(result, qt.Equals, test.expect)
|
b.Assert(result, qt.Equals, test.expect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHTMLUnescape(t *testing.T) {
|
func TestHTMLUnescape(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
c := qt.New(t)
|
b := hugolib.NewIntegrationTestBuilder(
|
||||||
|
hugolib.IntegrationTestConfig{T: t},
|
||||||
|
).Build()
|
||||||
|
|
||||||
v := config.New()
|
ns := transform.New(b.H.Deps)
|
||||||
v.Set("contentDir", "content")
|
|
||||||
ns := New(newDeps(v))
|
|
||||||
|
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
s interface{}
|
s interface{}
|
||||||
|
@ -142,23 +145,23 @@ func TestHTMLUnescape(t *testing.T) {
|
||||||
|
|
||||||
result, err := ns.HTMLUnescape(test.s)
|
result, err := ns.HTMLUnescape(test.s)
|
||||||
|
|
||||||
if b, ok := test.expect.(bool); ok && !b {
|
if bb, ok := test.expect.(bool); ok && !bb {
|
||||||
c.Assert(err, qt.Not(qt.IsNil))
|
b.Assert(err, qt.Not(qt.IsNil))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Assert(err, qt.IsNil)
|
b.Assert(err, qt.IsNil)
|
||||||
c.Assert(result, qt.Equals, test.expect)
|
b.Assert(result, qt.Equals, test.expect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMarkdownify(t *testing.T) {
|
func TestMarkdownify(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
c := qt.New(t)
|
b := hugolib.NewIntegrationTestBuilder(
|
||||||
|
hugolib.IntegrationTestConfig{T: t},
|
||||||
|
).Build()
|
||||||
|
|
||||||
v := config.New()
|
ns := transform.New(b.H.Deps)
|
||||||
v.Set("contentDir", "content")
|
|
||||||
ns := New(newDeps(v))
|
|
||||||
|
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
s interface{}
|
s interface{}
|
||||||
|
@ -171,23 +174,24 @@ func TestMarkdownify(t *testing.T) {
|
||||||
|
|
||||||
result, err := ns.Markdownify(test.s)
|
result, err := ns.Markdownify(test.s)
|
||||||
|
|
||||||
if b, ok := test.expect.(bool); ok && !b {
|
if bb, ok := test.expect.(bool); ok && !bb {
|
||||||
c.Assert(err, qt.Not(qt.IsNil))
|
b.Assert(err, qt.Not(qt.IsNil))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Assert(err, qt.IsNil)
|
b.Assert(err, qt.IsNil)
|
||||||
c.Assert(result, qt.Equals, test.expect)
|
b.Assert(result, qt.Equals, test.expect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue #3040
|
// Issue #3040
|
||||||
func TestMarkdownifyBlocksOfText(t *testing.T) {
|
func TestMarkdownifyBlocksOfText(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
c := qt.New(t)
|
b := hugolib.NewIntegrationTestBuilder(
|
||||||
v := config.New()
|
hugolib.IntegrationTestConfig{T: t},
|
||||||
v.Set("contentDir", "content")
|
).Build()
|
||||||
ns := New(newDeps(v))
|
|
||||||
|
ns := transform.New(b.H.Deps)
|
||||||
|
|
||||||
text := `
|
text := `
|
||||||
#First
|
#First
|
||||||
|
@ -202,17 +206,18 @@ And then some.
|
||||||
`
|
`
|
||||||
|
|
||||||
result, err := ns.Markdownify(text)
|
result, err := ns.Markdownify(text)
|
||||||
c.Assert(err, qt.IsNil)
|
b.Assert(err, qt.IsNil)
|
||||||
c.Assert(result, qt.Equals, template.HTML(
|
b.Assert(result, qt.Equals, template.HTML(
|
||||||
"<p>#First</p>\n<p>This is some <em>bold</em> text.</p>\n<h2 id=\"second\">Second</h2>\n<p>This is some more text.</p>\n<p>And then some.</p>\n"))
|
"<p>#First</p>\n<p>This is some <em>bold</em> text.</p>\n<h2 id=\"second\">Second</h2>\n<p>This is some more text.</p>\n<p>And then some.</p>\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPlainify(t *testing.T) {
|
func TestPlainify(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
c := qt.New(t)
|
b := hugolib.NewIntegrationTestBuilder(
|
||||||
|
hugolib.IntegrationTestConfig{T: t},
|
||||||
|
).Build()
|
||||||
|
|
||||||
v := config.New()
|
ns := transform.New(b.H.Deps)
|
||||||
ns := New(newDeps(v))
|
|
||||||
|
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
s interface{}
|
s interface{}
|
||||||
|
@ -225,13 +230,13 @@ func TestPlainify(t *testing.T) {
|
||||||
|
|
||||||
result, err := ns.Plainify(test.s)
|
result, err := ns.Plainify(test.s)
|
||||||
|
|
||||||
if b, ok := test.expect.(bool); ok && !b {
|
if bb, ok := test.expect.(bool); ok && !bb {
|
||||||
c.Assert(err, qt.Not(qt.IsNil))
|
b.Assert(err, qt.Not(qt.IsNil))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Assert(err, qt.IsNil)
|
b.Assert(err, qt.IsNil)
|
||||||
c.Assert(result, qt.Equals, test.expect)
|
b.Assert(result, qt.Equals, test.expect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
package transform
|
package transform_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -19,7 +19,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/config"
|
"github.com/gohugoio/hugo/hugolib"
|
||||||
|
"github.com/gohugoio/hugo/tpl/transform"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/hugio"
|
"github.com/gohugoio/hugo/common/hugio"
|
||||||
"github.com/gohugoio/hugo/resources/resource"
|
"github.com/gohugoio/hugo/resources/resource"
|
||||||
|
@ -80,12 +81,14 @@ func (t testContentResource) Key() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnmarshal(t *testing.T) {
|
func TestUnmarshal(t *testing.T) {
|
||||||
v := config.New()
|
b := hugolib.NewIntegrationTestBuilder(
|
||||||
ns := New(newDeps(v))
|
hugolib.IntegrationTestConfig{T: t},
|
||||||
c := qt.New(t)
|
).Build()
|
||||||
|
|
||||||
|
ns := transform.New(b.H.Deps)
|
||||||
|
|
||||||
assertSlogan := func(m map[string]interface{}) {
|
assertSlogan := func(m map[string]interface{}) {
|
||||||
c.Assert(m["slogan"], qt.Equals, "Hugo Rocks!")
|
b.Assert(m["slogan"], qt.Equals, "Hugo Rocks!")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range []struct {
|
for _, test := range []struct {
|
||||||
|
@ -116,24 +119,24 @@ func TestUnmarshal(t *testing.T) {
|
||||||
}},
|
}},
|
||||||
{testContentResource{key: "r1", content: `1997,Ford,E350,"ac, abs, moon",3000.00
|
{testContentResource{key: "r1", content: `1997,Ford,E350,"ac, abs, moon",3000.00
|
||||||
1999,Chevy,"Venture ""Extended Edition""","",4900.00`, mime: media.CSVType}, nil, func(r [][]string) {
|
1999,Chevy,"Venture ""Extended Edition""","",4900.00`, mime: media.CSVType}, nil, func(r [][]string) {
|
||||||
c.Assert(len(r), qt.Equals, 2)
|
b.Assert(len(r), qt.Equals, 2)
|
||||||
first := r[0]
|
first := r[0]
|
||||||
c.Assert(len(first), qt.Equals, 5)
|
b.Assert(len(first), qt.Equals, 5)
|
||||||
c.Assert(first[1], qt.Equals, "Ford")
|
b.Assert(first[1], qt.Equals, "Ford")
|
||||||
}},
|
}},
|
||||||
{testContentResource{key: "r1", content: `a;b;c`, mime: media.CSVType}, map[string]interface{}{"delimiter": ";"}, func(r [][]string) {
|
{testContentResource{key: "r1", content: `a;b;c`, mime: media.CSVType}, map[string]interface{}{"delimiter": ";"}, func(r [][]string) {
|
||||||
c.Assert([][]string{{"a", "b", "c"}}, qt.DeepEquals, r)
|
b.Assert([][]string{{"a", "b", "c"}}, qt.DeepEquals, r)
|
||||||
}},
|
}},
|
||||||
{"a,b,c", nil, func(r [][]string) {
|
{"a,b,c", nil, func(r [][]string) {
|
||||||
c.Assert([][]string{{"a", "b", "c"}}, qt.DeepEquals, r)
|
b.Assert([][]string{{"a", "b", "c"}}, qt.DeepEquals, r)
|
||||||
}},
|
}},
|
||||||
{"a;b;c", map[string]interface{}{"delimiter": ";"}, func(r [][]string) {
|
{"a;b;c", map[string]interface{}{"delimiter": ";"}, func(r [][]string) {
|
||||||
c.Assert([][]string{{"a", "b", "c"}}, qt.DeepEquals, r)
|
b.Assert([][]string{{"a", "b", "c"}}, qt.DeepEquals, r)
|
||||||
}},
|
}},
|
||||||
{testContentResource{key: "r1", content: `
|
{testContentResource{key: "r1", content: `
|
||||||
% This is a comment
|
% This is a comment
|
||||||
a;b;c`, mime: media.CSVType}, map[string]interface{}{"DElimiter": ";", "Comment": "%"}, func(r [][]string) {
|
a;b;c`, mime: media.CSVType}, map[string]interface{}{"DElimiter": ";", "Comment": "%"}, func(r [][]string) {
|
||||||
c.Assert([][]string{{"a", "b", "c"}}, qt.DeepEquals, r)
|
b.Assert([][]string{{"a", "b", "c"}}, qt.DeepEquals, r)
|
||||||
}},
|
}},
|
||||||
// errors
|
// errors
|
||||||
{"thisisnotavaliddataformat", nil, false},
|
{"thisisnotavaliddataformat", nil, false},
|
||||||
|
@ -144,7 +147,7 @@ a;b;c`, mime: media.CSVType}, map[string]interface{}{"DElimiter": ";", "Comment"
|
||||||
{tstNoStringer{}, nil, false},
|
{tstNoStringer{}, nil, false},
|
||||||
} {
|
} {
|
||||||
|
|
||||||
ns.cache.Clear()
|
ns.Reset()
|
||||||
|
|
||||||
var args []interface{}
|
var args []interface{}
|
||||||
|
|
||||||
|
@ -156,29 +159,32 @@ a;b;c`, mime: media.CSVType}, map[string]interface{}{"DElimiter": ";", "Comment"
|
||||||
|
|
||||||
result, err := ns.Unmarshal(args...)
|
result, err := ns.Unmarshal(args...)
|
||||||
|
|
||||||
if b, ok := test.expect.(bool); ok && !b {
|
if bb, ok := test.expect.(bool); ok && !bb {
|
||||||
c.Assert(err, qt.Not(qt.IsNil))
|
b.Assert(err, qt.Not(qt.IsNil))
|
||||||
} else if fn, ok := test.expect.(func(m map[string]interface{})); ok {
|
} else if fn, ok := test.expect.(func(m map[string]interface{})); ok {
|
||||||
c.Assert(err, qt.IsNil)
|
b.Assert(err, qt.IsNil)
|
||||||
m, ok := result.(map[string]interface{})
|
m, ok := result.(map[string]interface{})
|
||||||
c.Assert(ok, qt.Equals, true)
|
b.Assert(ok, qt.Equals, true)
|
||||||
fn(m)
|
fn(m)
|
||||||
} else if fn, ok := test.expect.(func(r [][]string)); ok {
|
} else if fn, ok := test.expect.(func(r [][]string)); ok {
|
||||||
c.Assert(err, qt.IsNil)
|
b.Assert(err, qt.IsNil)
|
||||||
r, ok := result.([][]string)
|
r, ok := result.([][]string)
|
||||||
c.Assert(ok, qt.Equals, true)
|
b.Assert(ok, qt.Equals, true)
|
||||||
fn(r)
|
fn(r)
|
||||||
} else {
|
} else {
|
||||||
c.Assert(err, qt.IsNil)
|
b.Assert(err, qt.IsNil)
|
||||||
c.Assert(result, qt.Equals, test.expect)
|
b.Assert(result, qt.Equals, test.expect)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkUnmarshalString(b *testing.B) {
|
func BenchmarkUnmarshalString(b *testing.B) {
|
||||||
v := config.New()
|
bb := hugolib.NewIntegrationTestBuilder(
|
||||||
ns := New(newDeps(v))
|
hugolib.IntegrationTestConfig{T: b},
|
||||||
|
).Build()
|
||||||
|
|
||||||
|
ns := transform.New(bb.H.Deps)
|
||||||
|
|
||||||
const numJsons = 100
|
const numJsons = 100
|
||||||
|
|
||||||
|
@ -200,8 +206,11 @@ func BenchmarkUnmarshalString(b *testing.B) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkUnmarshalResource(b *testing.B) {
|
func BenchmarkUnmarshalResource(b *testing.B) {
|
||||||
v := config.New()
|
bb := hugolib.NewIntegrationTestBuilder(
|
||||||
ns := New(newDeps(v))
|
hugolib.IntegrationTestConfig{T: b},
|
||||||
|
).Build()
|
||||||
|
|
||||||
|
ns := transform.New(bb.H.Deps)
|
||||||
|
|
||||||
const numJsons = 100
|
const numJsons = 100
|
||||||
|
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
// Copyright 2017 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 urls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/config"
|
|
||||||
|
|
||||||
qt "github.com/frankban/quicktest"
|
|
||||||
"github.com/gohugoio/hugo/deps"
|
|
||||||
"github.com/gohugoio/hugo/htesting/hqt"
|
|
||||||
"github.com/gohugoio/hugo/tpl/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInit(t *testing.T) {
|
|
||||||
c := qt.New(t)
|
|
||||||
var found bool
|
|
||||||
var ns *internal.TemplateFuncsNamespace
|
|
||||||
|
|
||||||
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
|
|
||||||
ns = nsf(&deps.Deps{Cfg: config.New()})
|
|
||||||
if ns.Name == name {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(found, qt.Equals, true)
|
|
||||||
ctx, err := ns.Context()
|
|
||||||
c.Assert(err, qt.IsNil)
|
|
||||||
c.Assert(ctx, hqt.IsSameType, &Namespace{})
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in a new issue