diff --git a/common/hugio/writers.go b/common/hugio/writers.go index 82c4dca52e4..d8be83a4043 100644 --- a/common/hugio/writers.go +++ b/common/hugio/writers.go @@ -18,6 +18,14 @@ import ( "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 { io.Writer closers []io.WriteCloser diff --git a/docs/content/en/content-management/diagrams.md b/docs/content/en/content-management/diagrams.md new file mode 100644 index 00000000000..5158adbb4e2 --- /dev/null +++ b/docs/content/en/content-management/diagrams.md @@ -0,0 +1,199 @@ +--- +title: Diagrams +date: 2022-02-20 +categories: [content management] +keywords: [diagrams,drawing] +menu: + docs: + parent: "content-management" + weight: 22 +weight: 22 +toc: true +--- + + +## 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 } """" .│ +└────────────────────────────────────────────────┘ +``` + + + diff --git a/docs/layouts/_default/_markup/render-codeblock-goat.html b/docs/layouts/_default/_markup/render-codeblock-goat.html new file mode 100644 index 00000000000..b1e57e94a7b --- /dev/null +++ b/docs/layouts/_default/_markup/render-codeblock-goat.html @@ -0,0 +1,18 @@ +{{ $width := .Attributes.width }} +{{ $height := .Attributes.height }} +{{ $class := .Attributes.class | default "" }} +
+ {{ with diagrams.Goat .Code }} + + {{ .Body }} + + {{ end }} +
diff --git a/docs/layouts/_default/render-codeblock-mermaid.html b/docs/layouts/_default/render-codeblock-mermaid.html new file mode 100644 index 00000000000..ada09469518 --- /dev/null +++ b/docs/layouts/_default/render-codeblock-mermaid.html @@ -0,0 +1 @@ +{{- .Code | safeHTML }} diff --git a/go.mod b/go.mod index df37a22dcc6..491517d40b9 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/aws/aws-sdk-go v1.41.14 github.com/bep/debounce v1.2.0 github.com/bep/gitmap v1.1.2 + github.com/bep/goat v0.0.0-20220222160823-cc97a132eb5e github.com/bep/godartsass v0.12.0 github.com/bep/golibsass v1.0.0 github.com/bep/gowebp v0.1.0 @@ -19,7 +20,7 @@ require ( github.com/dustin/go-humanize v1.0.0 github.com/evanw/esbuild v0.14.22 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/getkin/kin-openapi v0.85.0 github.com/ghodss/yaml v1.0.0 @@ -29,7 +30,7 @@ require ( github.com/gohugoio/locales v0.14.0 github.com/gohugoio/localescompressed v0.15.0 github.com/gohugoio/testmodBuilder/mods v0.0.0-20190520184928-c56af20f2e95 - github.com/google/go-cmp v0.5.6 + github.com/google/go-cmp v0.5.7 github.com/gorilla/websocket v1.4.2 github.com/jdkato/prose v1.2.1 github.com/kylelemons/godebug v1.1.0 @@ -57,7 +58,7 @@ require ( github.com/spf13/viper v1.10.1 github.com/tdewolff/minify/v2 v2.9.29 github.com/yuin/goldmark v1.4.7 - github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 + github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 // indirect gocloud.dev v0.20.0 golang.org/x/image v0.0.0-20211028202545-6944b10bf410 golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f diff --git a/go.sum b/go.sum index e4bdfa05d93..56f83b6d100 100644 --- a/go.sum +++ b/go.sum @@ -99,25 +99,18 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= -github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20191009163259-e802c2cb94ae/go.mod h1:mjwGPas4yKduTyubHvD1Atl9r1rUq8DfVy+gkVvZ+oo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= -github.com/alecthomas/chroma v0.7.2-0.20200305040604-4f3623dce67a/go.mod h1:fv5SzZPFJbwp2NXJWpFIX7DZS4HgV1K4ew4Pc2OZD9s= github.com/alecthomas/chroma v0.8.2/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM= github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= -github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= -github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= -github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA= github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -142,6 +135,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/gitmap v1.1.2 h1:zk04w1qc1COTZPPYWDQHvns3y1afOsdRfraFQ3qI840= github.com/bep/gitmap v1.1.2/go.mod h1:g9VRETxFUXNWzMiuxOwcudo6DfZkW9jOsOW0Ft4kYaY= +github.com/bep/goat v0.0.0-20220220225353-2944b4d89c7b h1:AiTAWIFRCBGP9C6uHfVYsBANDEmYxg2k4skJ46Z0A6Q= +github.com/bep/goat v0.0.0-20220220225353-2944b4d89c7b/go.mod h1:Md9x7gRxiWKs85yHlVTvHQw9rg86Bm+Y4SuYE8CTH7c= +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/godartsass v0.12.0 h1:VvGLA4XpXUjKvp53SI05YFLhRFJ78G+Ybnlaz6Oul7E= github.com/bep/godartsass v0.12.0/go.mod h1:nXQlHHk4H1ghUk6n/JkYKG5RD43yJfcfp5aHRqT/pc4= github.com/bep/golibsass v1.0.0 h1:gNguBMSDi5yZEZzVZP70YpuFQE3qogJIGUlrVILTmOw= @@ -192,7 +189,6 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -206,7 +202,6 @@ github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQ github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc= github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE= -github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= @@ -236,8 +231,8 @@ github.com/frankban/quicktest v1.4.1/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60 github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= github.com/frankban/quicktest v1.11.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s= 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/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.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= @@ -322,8 +317,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-replayers/grpcreplay v0.1.0 h1:eNb1y9rZFmY4ax45uEEECSa8fsxGRU+8Bil52ASAwic= github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE= github.com/google/go-replayers/httpreplay v0.1.0 h1:AX7FUb4BjrrzNvblr/OlgwrmFiep6soj5K2QSDW7BGk= @@ -366,11 +362,7 @@ github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pf github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI= -github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= @@ -416,7 +408,6 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jdkato/prose v1.2.1 h1:Fp3UnJmLVISmlc57BgKUzdjr0lOtjqTZicL3PaYy6cU= github.com/jdkato/prose v1.2.1/go.mod h1:AiRHgVagnEx2JbQRQowVBKjG0bcs/vtkGCH1dYAL1rA= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= @@ -471,7 +462,6 @@ github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HN github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI= github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= @@ -513,7 +503,6 @@ github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/niklasfasching/go-org v1.6.0 h1:NCWpmDDNjHNsrei6VmnYXzOiyZUxV8LVU19REGQ8dKA= github.com/niklasfasching/go-org v1.6.0/go.mod h1:gSHyFcAbr2thIpfljLsP/BB8uwAMyooq6ydIrUDdOCs= -github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -609,21 +598,17 @@ github.com/tdewolff/parse/v2 v2.5.27/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1 github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4= github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= -github.com/yuin/goldmark v1.1.22/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.4 h1:zNWRjYUW32G9KirMXYHQHVNFkXvMI7LpgNW2AgYAoIs= -github.com/yuin/goldmark v1.4.4/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg= +github.com/yuin/goldmark v1.4.5/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg= github.com/yuin/goldmark v1.4.7 h1:KHHlQL4EKBZ43vpA1KBEQHfodk4JeIgeb0xJLg7rvDI= github.com/yuin/goldmark v1.4.7/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg= -github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691 h1:VWSxtAiQNh3zgHJpdpkpVYjTPqRE3P6UZCOPa1nRDio= -github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691/go.mod h1:YLF3kDffRfUH/bTxOxHhV6lxwIB3Vfj91rEwNMS9MXo= +github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 h1:yHfZyN55+5dp1wG7wDKv8HQ044moxkyGq12KFFMFDxg= +github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594/go.mod h1:U9ihbh+1ZN7fR5Se3daSPoz1CGF9IYtSvWwVQtnzGHU= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= @@ -784,7 +769,6 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/helpers/content.go b/helpers/content.go index 157f75079ec..41bbabf68e2 100644 --- a/helpers/content.go +++ b/helpers/content.go @@ -30,6 +30,7 @@ import ( "github.com/spf13/afero" "github.com/gohugoio/hugo/markup/converter" + "github.com/gohugoio/hugo/markup/converter/hooks" "github.com/gohugoio/hugo/markup" @@ -49,6 +50,7 @@ type ContentSpec struct { Converters markup.ConverterProvider MardownConverter converter.Converter // Markdown converter with no document context 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 int @@ -193,7 +195,7 @@ func ExtractTOC(content []byte) (newcontent []byte, toc []byte) { } func (c *ContentSpec) RenderMarkdown(src []byte) ([]byte, error) { - b, err := c.MardownConverter.Convert(converter.RenderContext{Src: src}) + b, err := c.MardownConverter.Convert(converter.RenderContext{Src: src, GetRenderer: c.getRenderer}) if err != nil { return nil, err } diff --git a/hugolib/content_render_hooks_test.go b/hugolib/content_render_hooks_test.go index edfeaa82a81..33ebe1f41d9 100644 --- a/hugolib/content_render_hooks_test.go +++ b/hugolib/content_render_hooks_test.go @@ -231,8 +231,8 @@ SHORT3| 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`) b.AssertFileContent("public/blog/p4/index.html", `

IMAGE: Cool Page With Image||/images/Dragster.jpg|Title: image title|Text: Drag Racing|END

`) - // The regular markdownify func currently gets regular links. - b.AssertFileContent("public/blog/p5/index.html", "Inner Link: Inner Link\n") + // markdownify + 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", "Inner Inline: Inner Link: With RenderString|https://www.gohugo.io|Title: Hugo's Homepage|Text: Inner Link|END", diff --git a/hugolib/integrationtest_builder.go b/hugolib/integrationtest_builder.go index 7ec7a150325..ed68783a165 100644 --- a/hugolib/integrationtest_builder.go +++ b/hugolib/integrationtest_builder.go @@ -125,7 +125,7 @@ func (s *IntegrationTestBuilder) AssertFileContent(filename string, matches ...s if match == "" || strings.HasPrefix(match, "#") { 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 { s.Helper() _, err := s.BuildE() - if s.Cfg.Verbose { + if s.Cfg.Verbose || err != nil { fmt.Println(s.logBuff.String()) } s.Assert(err, qt.IsNil) diff --git a/hugolib/language_content_dir_test.go b/hugolib/language_content_dir_test.go index 117fdfb1431..9a7a78e7e3f 100644 --- a/hugolib/language_content_dir_test.go +++ b/hugolib/language_content_dir_test.go @@ -314,7 +314,7 @@ Content. nnSect := nnSite.getPage(page.KindSection, "sect") c.Assert(nnSect, qt.Not(qt.IsNil)) c.Assert(len(nnSect.Pages()), qt.Equals, 12) - nnHome, _ := nnSite.Info.Home() + nnHome := nnSite.Info.Home() c.Assert(nnHome.RelPermalink(), qt.Equals, "/nn/") } diff --git a/hugolib/page.go b/hugolib/page.go index 11b41e16920..9229284c2c3 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -21,6 +21,7 @@ import ( "path/filepath" "sort" "strings" + "sync/atomic" "github.com/gohugoio/hugo/identity" @@ -47,7 +48,6 @@ import ( "github.com/gohugoio/hugo/common/collections" "github.com/gohugoio/hugo/common/text" - "github.com/gohugoio/hugo/markup/converter/hooks" "github.com/gohugoio/hugo/resources" "github.com/gohugoio/hugo/resources/page" "github.com/gohugoio/hugo/resources/resource" @@ -118,6 +118,9 @@ type pageState struct { // formats (for all sites). pageOutputs []*pageOutput + // Used to determine if we can reuse content across output formats. + pageOutputTemplateVariationsState uint32 + // This will be shifted out when we start to render a new output format. *pageOutput @@ -125,6 +128,10 @@ type pageState struct { *pageCommon } +func (p *pageState) reusePageOutputContent() bool { + return atomic.LoadUint32(&p.pageOutputTemplateVariationsState) == 1 +} + func (p *pageState) Err() error { return nil } @@ -390,56 +397,6 @@ func (ps *pageState) initCommonProviders(pp pagePaths) error { 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 { p.layoutDescriptorInit.Do(func() { var section string @@ -617,6 +574,7 @@ func (p *pageState) wrapError(err error) error { return err } +// TODO1 remove1 func (p *pageState) getContentConverter() converter.Converter { var err error p.m.contentConverterInit.Do(func() { @@ -863,7 +821,7 @@ func (p *pageState) shiftToOutputFormat(isRenderingSite bool, idx int) error { if isRenderingSite { cp := p.pageOutput.cp - if cp == nil { + if cp == nil && p.reusePageOutputContent() { // Look for content to reuse. for i := 0; i < len(p.pageOutputs); i++ { if i == idx { @@ -871,7 +829,7 @@ func (p *pageState) shiftToOutputFormat(isRenderingSite bool, idx int) error { } po := p.pageOutputs[i] - if po.cp != nil && po.cp.reuse { + if po.cp != nil { cp = po.cp break } diff --git a/hugolib/page__per_output.go b/hugolib/page__per_output.go index bd4e35a5b0c..d980ca0d08b 100644 --- a/hugolib/page__per_output.go +++ b/hugolib/page__per_output.go @@ -21,6 +21,7 @@ import ( "runtime/debug" "strings" "sync" + "sync/atomic" "unicode/utf8" "github.com/gohugoio/hugo/identity" @@ -32,6 +33,7 @@ import ( "github.com/gohugoio/hugo/markup/converter" + "github.com/alecthomas/chroma/lexers" "github.com/gohugoio/hugo/lazy" bp "github.com/gohugoio/hugo/bufferpool" @@ -79,7 +81,6 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err dependencyTracker: dependencyTracker, p: p, f: po.f, - renderHooks: &renderHooks{}, } initContent := func() (err error) { @@ -109,16 +110,8 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err return err } - enableReuse := !(hasShortcodeVariants || cp.renderHooksHaveVariants) - - 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() + if hasShortcodeVariants { + atomic.StoreUint32(&p.pageOutputTemplateVariationsState, 2) } cp.workContent = p.contentToRender(cp.contentPlaceholders) @@ -199,19 +192,10 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err return nil } - // Recursive loops can only happen in content files with template code (shortcodes etc.) - // 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) { - return nil, initContent() - }) - } else { - cp.initMain = parent.Branch(func() (interface{}, error) { - return nil, initContent() - }) - } + // There may be recursive loops in shortcodes and render hooks. + cp.initMain = parent.BranchWithTimeout(p.s.siteCfg.timeout, func(ctx context.Context) (interface{}, error) { + return nil, initContent() + }) cp.initPlain = cp.initMain.Branch(func() (interface{}, error) { cp.plain = helpers.StripHTML(string(cp.content)) @@ -228,19 +212,10 @@ func newPageContentOutput(p *pageState, po *pageOutput) (*pageContentOutput, err return cp, nil } -type renderHooks struct { - hooks hooks.Renderers - init sync.Once -} - // pageContentOutput represents the Page content for a given output format. type pageContentOutput struct { f output.Format - // If we can reuse this for other output formats. - reuse bool - reuseInit sync.Once - p *pageState // Lazy load dependencies @@ -250,12 +225,8 @@ type pageContentOutput struct { placeholdersEnabled bool placeholdersEnabledInit sync.Once - 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 + // Renders Markdown hooks. + getRenderer hooks.GetRendererFunc workContent []byte dependencyTracker identity.Manager // Set in server mode. @@ -291,7 +262,7 @@ func (p *pageContentOutput) Reset() { } p.initMain.Reset() p.initPlain.Reset() - p.renderHooks = &renderHooks{} + p.getRenderer = nil } func (p *pageContentOutput) Content() (interface{}, error) { @@ -440,55 +411,105 @@ func (p *pageContentOutput) initRenderHooks() error { return nil } - var initErr error + if atomic.LoadUint32(&p.p.pageOutputTemplateVariationsState) == 0 { + atomic.StoreUint32(&p.p.pageOutputTemplateVariationsState, 1) + } + + type cacheKey struct { + tp hooks.RendererType + id interface{} + f output.Format + } - p.renderHooks.init.Do(func() { - ps := p.p + renderCache := make(map[cacheKey]interface{}) + var renderCacheMu sync.Mutex - c := ps.getContentConverter() - if c == nil || !c.Supports(converter.FeatureRenderHooks) { - return - } + p.getRenderer = func(tp hooks.RendererType, id interface{}) interface{} { + renderCacheMu.Lock() + defer renderCacheMu.Unlock() - h, err := ps.createRenderHooks(p.f) - if err != nil { - initErr = err - return + key := cacheKey{tp: tp, id: id, f: p.f} + if r, ok := renderCache[key]; ok { + return r } - p.renderHooks.hooks = h - - 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() { - continue - } + 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 + } + } + } - if p.renderHooks.hooks.IsZero() { - p.renderHooks.hooks = h2 - } + getHookTemplate := func(f output.Format) (tpl.Template, bool) { + templ, found, err := p.p.s.Tmpl().LookupLayout(layoutDescriptor, f) + if err != nil { + panic(err) + } + return templ, found + } - p.renderHooksHaveVariants = !h2.Eq(p.renderHooks.hooks) + templ, found1 := getHookTemplate(p.f) - if p.renderHooksHaveVariants { + 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 + } + templ2, found2 := getHookTemplate(f) + if found2 { + if !found1 { + templ = templ2 + found1 = true break } + if templ != templ2 { + atomic.StoreUint32(&p.p.pageOutputTemplateVariationsState, 2) + break + } } } } - }) - return initErr + 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 nil } func (p *pageContentOutput) setAutoSummary() error { @@ -511,17 +532,22 @@ func (p *pageContentOutput) setAutoSummary() error { return nil } +// TODO1 check usage func (cp *pageContentOutput) renderContent(content []byte, renderTOC bool) (converter.Result, error) { + cp.initRenderHooks() c := cp.p.getContentConverter() return cp.renderContentWithConverter(c, content, renderTOC) } func (cp *pageContentOutput) renderContentWithConverter(c converter.Converter, content []byte, renderTOC bool) (converter.Result, error) { + if cp.getRenderer == nil { + panic("getRenderer not set") + } r, err := c.Convert( converter.RenderContext{ Src: content, RenderTOC: renderTOC, - RenderHooks: cp.renderHooks.hooks, + GetRenderer: cp.getRenderer, }) if err == nil { @@ -570,12 +596,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. type pagePerOutputProviders interface { targetPather diff --git a/hugolib/page_test.go b/hugolib/page_test.go index 6b35e48144a..d8b306245d4 100644 --- a/hugolib/page_test.go +++ b/hugolib/page_test.go @@ -428,8 +428,7 @@ func testAllMarkdownEnginesForPages(t *testing.T, assertFunc(t, e.ext, s.RegularPages()) - home, err := s.Info.Home() - b.Assert(err, qt.IsNil) + home := s.Info.Home() b.Assert(home, qt.Not(qt.IsNil)) b.Assert(home.File().Path(), qt.Equals, homePath) b.Assert(content(home), qt.Contains, "Home Page Content") @@ -1284,7 +1283,7 @@ func TestTranslationKey(t *testing.T) { 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.TranslationKey(), qt.Equals, "home") c.Assert(s.RegularPages()[0].TranslationKey(), qt.Equals, "page/k1") diff --git a/hugolib/pagebundler_test.go b/hugolib/pagebundler_test.go index 1694b02ee8a..238c725bd86 100644 --- a/hugolib/pagebundler_test.go +++ b/hugolib/pagebundler_test.go @@ -150,7 +150,7 @@ func TestPageBundlerSiteRegular(t *testing.T) { c.Assert(leafBundle1.Section(), qt.Equals, "b") sectionB := s.getPage(page.KindSection, "b") c.Assert(sectionB, qt.Not(qt.IsNil)) - home, _ := s.Info.Home() + home := s.Info.Home() c.Assert(home.BundleType(), qt.Equals, files.ContentClassBranch) // This is a root bundle and should live in the "home section" @@ -290,7 +290,7 @@ func TestPageBundlerSiteMultilingual(t *testing.T) { c.Assert(len(s.RegularPages()), qt.Equals, 8) c.Assert(len(s.Pages()), qt.Equals, 16) - //dumpPages(s.AllPages()...) + // dumpPages(s.AllPages()...) c.Assert(len(s.AllPages()), qt.Equals, 31) diff --git a/hugolib/site.go b/hugolib/site.go index 02380a6e73c..881d5ab77fe 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -30,6 +30,7 @@ import ( "strings" "time" + "github.com/gohugoio/hugo/common/hugio" "github.com/gohugoio/hugo/common/types" "github.com/gohugoio/hugo/modules" "golang.org/x/text/unicode/norm" @@ -1773,19 +1774,23 @@ var infoOnMissingLayout = map[string]bool{ "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. -type hookRenderer struct { +type hookRendererTemplate struct { templateHandler tpl.TemplateHandler identity.SearchProvider 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) } -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) } diff --git a/hugolib/site_sections.go b/hugolib/site_sections.go index ae343716eaa..4f8dd031433 100644 --- a/hugolib/site_sections.go +++ b/hugolib/site_sections.go @@ -19,14 +19,14 @@ import ( // Sections returns the top level sections. func (s *SiteInfo) Sections() page.Pages { - home, err := s.Home() - if err == nil { + home := s.Home() + if home != nil { return home.Sections() } return nil } // Home is a shortcut to the home page, equivalent to .Site.GetPage "home". -func (s *SiteInfo) Home() (page.Page, error) { - return s.s.home, nil +func (s *SiteInfo) Home() page.Page { + return s.s.home } diff --git a/markup/converter/converter.go b/markup/converter/converter.go index 180208a7bfc..30addfec657 100644 --- a/markup/converter/converter.go +++ b/markup/converter/converter.go @@ -21,6 +21,7 @@ import ( "github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/identity" "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/tableofcontents" "github.com/spf13/afero" @@ -34,7 +35,7 @@ type ProviderConfig struct { ContentFs afero.Fs Logger loggers.Logger Exec *hexec.Exec - Highlight func(code, lang, optsStr string) (string, error) + highlight.Highlighter } // ProviderProvider creates converter providers. @@ -127,9 +128,10 @@ type DocumentContext struct { // RenderContext holds contextual information about the content to render. type RenderContext struct { - Src []byte - RenderTOC bool - RenderHooks hooks.Renderers + Src []byte + RenderTOC bool + + GetRenderer hooks.GetRendererFunc } var FeatureRenderHooks = identity.NewPathIdentity("markup", "renderingHooks") diff --git a/markup/converter/hooks/hooks.go b/markup/converter/hooks/hooks.go index d36dad28806..987cb1dc36b 100644 --- a/markup/converter/hooks/hooks.go +++ b/markup/converter/hooks/hooks.go @@ -14,15 +14,17 @@ package hooks import ( - "fmt" "io" - "strings" + "github.com/gohugoio/hugo/common/hugio" "github.com/gohugoio/hugo/identity" + "github.com/gohugoio/hugo/markup/internal/attributes" ) +var _ AttributesOptionsSliceProvider = (*attributes.AttributesHolder)(nil) + type AttributesProvider interface { - Attributes() map[string]string + Attributes() map[string]interface{} } type LinkContext interface { @@ -33,11 +35,30 @@ type LinkContext interface { 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 { RenderLink(w io.Writer, ctx LinkContext) error identity.Provider } +type CodeBlockRenderer interface { + RenderCodeblock(w hugio.FlexiWriter, ctx CodeblockContext) error + identity.Provider +} + // HeadingContext contains accessors to all attributes that a HeadingRenderer // can use to render a heading. type HeadingContext interface { @@ -63,70 +84,13 @@ type HeadingRenderer interface { identity.Provider } -type Renderers struct { - LinkRenderer LinkRenderer - ImageRenderer LinkRenderer - HeadingRenderer HeadingRenderer -} - -func (r Renderers) Eq(other interface{}) bool { - ro, ok := other.(Renderers) - if !ok { - return false - } - - if r.IsZero() || ro.IsZero() { - 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 -} +type RendererType int -func (r Renderers) String() string { - if r.IsZero() { - return "" - } - - 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())) - } +const ( + LinkRendererType RendererType = iota + 1 + ImageRendererType + HeadingRendererType + CodeBlockRendererType +) - return sb.String() -} +type GetRendererFunc func(t RendererType, id interface{}) interface{} diff --git a/markup/goldmark/codeblocks/integration_test.go b/markup/goldmark/codeblocks/integration_test.go new file mode 100644 index 00000000000..d662b39118e --- /dev/null +++ b/markup/goldmark/codeblocks/integration_test.go @@ -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:Go Code\nGo Code: fmt.Println(\"Hello, World!\");\n|\nGo Language: go|", + "

Golang Code

\nGo Code: fmt.Println(\"Hello, Golang!\");\n|\nGo Language: golang|", + "

Bash Code

\n
32echo "l1";\n33",
+	)
+}
diff --git a/markup/goldmark/codeblocks/render.go b/markup/goldmark/codeblocks/render.go
new file mode 100644
index 00000000000..59d142e23d0
--- /dev/null
+++ b/markup/goldmark/codeblocks/render.go
@@ -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
+}
diff --git a/markup/goldmark/codeblocks/transform.go b/markup/goldmark/codeblocks/transform.go
new file mode 100644
index 00000000000..791e99a5c3c
--- /dev/null
+++ b/markup/goldmark/codeblocks/transform.go
@@ -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)
+		}
+	}
+}
diff --git a/markup/goldmark/convert.go b/markup/goldmark/convert.go
index c547fe1e0d1..bced48f4636 100644
--- a/markup/goldmark/convert.go
+++ b/markup/goldmark/convert.go
@@ -17,12 +17,12 @@ package goldmark
 import (
 	"bytes"
 	"fmt"
-	"math/bits"
 	"path/filepath"
 	"runtime/debug"
 
+	"github.com/gohugoio/hugo/markup/goldmark/codeblocks"
 	"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"
 
@@ -32,16 +32,13 @@ import (
 
 	"github.com/gohugoio/hugo/hugofs"
 	"github.com/gohugoio/hugo/markup/converter"
-	"github.com/gohugoio/hugo/markup/highlight"
 	"github.com/gohugoio/hugo/markup/tableofcontents"
 	"github.com/yuin/goldmark"
-	hl "github.com/yuin/goldmark-highlighting"
 	"github.com/yuin/goldmark/extension"
 	"github.com/yuin/goldmark/parser"
 	"github.com/yuin/goldmark/renderer"
 	"github.com/yuin/goldmark/renderer/html"
 	"github.com/yuin/goldmark/text"
-	"github.com/yuin/goldmark/util"
 )
 
 // Provider is the package entry point.
@@ -104,7 +101,7 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
 	)
 
 	if mcfg.Highlight.CodeFences {
-		extensions = append(extensions, newHighlighting(mcfg.Highlight))
+		extensions = append(extensions, newCodeblocksExtender())
 	}
 
 	if cfg.Extensions.Table {
@@ -178,65 +175,6 @@ func (c converterResult) GetIdentities() identity.Identities {
 	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"}
 
 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
 	pctx := c.newParserContext(ctx)
 	reader := text.NewReader(ctx.Src)
@@ -261,15 +199,15 @@ func (c *goldmarkConverter) Convert(ctx converter.RenderContext) (result convert
 		parser.WithContext(pctx),
 	)
 
-	rcx := &renderContextDataHolder{
-		rctx: ctx,
-		dctx: c.ctx,
-		ids:  identity.NewManager(converterIdentity),
+	rcx := &render.RenderContextDataHolder{
+		Rctx: ctx,
+		Dctx: c.ctx,
+		IDs:  identity.NewManager(converterIdentity),
 	}
 
-	w := &renderContext{
-		bufWriter:         buf,
-		renderContextData: rcx,
+	w := &render.Context{
+		BufWriter:   buf,
+		ContextData: rcx,
 	}
 
 	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{
 		Result: buf,
-		ids:    rcx.ids.GetIdentities(),
+		ids:    rcx.IDs.GetIdentities(),
 		toc:    pctx.TableOfContents(),
 	}, nil
 }
@@ -310,62 +248,7 @@ func (p *parserContext) TableOfContents() 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(`
") -} - -func writeDivEnd(w util.BufWriter) { - w.WriteString("
") +func newCodeblocksExtender() goldmark.Extender { + // TODO1 check w.WriteString(`
echo "Hugo Rocks!"\n
") result = convertForConfig(c, cfg, `echo "Hugo Rocks!"`, "unknown") - c.Assert(result, qt.Equals, "
echo "Hugo Rocks!"\n
") + c.Assert(result, qt.Equals, "
echo "Hugo Rocks!"\n
") }) c.Run("Highlight lines, default config", func(c *qt.C) { diff --git a/markup/goldmark/integration_test.go b/markup/goldmark/integration_test.go index 4ace04f756b..4a3aefe86d6 100644 --- a/markup/goldmark/integration_test.go +++ b/markup/goldmark/integration_test.go @@ -132,6 +132,84 @@ title: "p1" ) } +// TODO1 +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 + +§§A§moo +LINE1 +§§A§ + +## 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", + //"
LINE1\n
", + //"Code Fences No Lexer\n
LINE1\n
", + "lnt", + ) +} + func BenchmarkRenderHooks(b *testing.B) { files := ` -- config.toml -- diff --git a/markup/goldmark/internal/render/context.go b/markup/goldmark/internal/render/context.go new file mode 100644 index 00000000000..b18983ef3b5 --- /dev/null +++ b/markup/goldmark/internal/render/context.go @@ -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) +} diff --git a/markup/goldmark/render_hooks.go b/markup/goldmark/render_hooks.go index 1862c212543..ef6245a0fbd 100644 --- a/markup/goldmark/render_hooks.go +++ b/markup/goldmark/render_hooks.go @@ -16,11 +16,10 @@ package goldmark import ( "bytes" "strings" - "sync" - - "github.com/spf13/cast" "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" @@ -44,28 +43,6 @@ func newLinks() goldmark.Extender { 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 { page interface{} destination string @@ -104,7 +81,7 @@ type headingContext struct { anchor string text string plainText string - *attributesHolder + *attributes.AttributesHolder } func (ctx headingContext) Page() interface{} { @@ -143,52 +120,17 @@ func (r *hookedRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) 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) { n := node.(*ast.Image) - var h hooks.Renderers + var lr hooks.LinkRenderer - ctx, ok := w.(*renderContext) + ctx, ok := w.(*render.Context) if ok { - h = ctx.RenderContext().RenderHooks - ok = h.ImageRenderer != nil + h := ctx.RenderContext().GetRenderer(hooks.ImageRendererType, nil) + ok = h != nil + if ok { + lr = h.(hooks.LinkRenderer) + } } if !ok { @@ -197,15 +139,15 @@ func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.N if entering { // 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 } - pos := ctx.popPos() + pos := ctx.PopPos() text := ctx.Buffer.Bytes()[pos:] ctx.Buffer.Truncate(pos) - err := h.ImageRenderer.RenderLink( + err := lr.RenderLink( w, linkContext{ 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 } @@ -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) { n := node.(*ast.Link) - var h hooks.Renderers + var lr hooks.LinkRenderer - ctx, ok := w.(*renderContext) + ctx, ok := w.(*render.Context) if ok { - h = ctx.RenderContext().RenderHooks - ok = h.LinkRenderer != nil + h := ctx.RenderContext().GetRenderer(hooks.LinkRendererType, nil) + ok = h != nil + if ok { + lr = h.(hooks.LinkRenderer) + } } if !ok { @@ -264,15 +209,15 @@ func (r *hookedRenderer) renderLink(w util.BufWriter, source []byte, node ast.No if entering { // 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 } - pos := ctx.popPos() + pos := ctx.PopPos() text := ctx.Buffer.Bytes()[pos:] ctx.Buffer.Truncate(pos) - err := h.LinkRenderer.RenderLink( + err := lr.RenderLink( w, linkContext{ 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, // 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. - ctx.AddIdentity(h.LinkRenderer) + ctx.AddIdentity(lr) return ast.WalkContinue, err } @@ -319,12 +264,15 @@ func (r *hookedRenderer) renderAutoLink(w util.BufWriter, source []byte, node as } n := node.(*ast.AutoLink) - var h hooks.Renderers + var lr hooks.LinkRenderer - ctx, ok := w.(*renderContext) + ctx, ok := w.(*render.Context) if ok { - h = ctx.RenderContext().RenderHooks - ok = h.LinkRenderer != nil + h := ctx.RenderContext().GetRenderer(hooks.LinkRendererType, nil) + ok = h != nil + if ok { + lr = h.(hooks.LinkRenderer) + } } if !ok { @@ -337,7 +285,7 @@ func (r *hookedRenderer) renderAutoLink(w util.BufWriter, source []byte, node as url = "mailto:" + url } - err := h.LinkRenderer.RenderLink( + err := lr.RenderLink( w, linkContext{ 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, // 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. - ctx.AddIdentity(h.LinkRenderer) + ctx.AddIdentity(lr) 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) { n := node.(*ast.Heading) - var h hooks.Renderers + var hr hooks.HeadingRenderer - ctx, ok := w.(*renderContext) + ctx, ok := w.(*render.Context) if ok { - h = ctx.RenderContext().RenderHooks - ok = h.HeadingRenderer != nil + h := ctx.RenderContext().GetRenderer(hooks.HeadingRendererType, nil) + ok = h != nil + if ok { + hr = h.(hooks.HeadingRenderer) + } } if !ok { @@ -397,11 +348,11 @@ func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast if entering { // 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 } - pos := ctx.popPos() + pos := ctx.PopPos() text := ctx.Buffer.Bytes()[pos:] ctx.Buffer.Truncate(pos) // 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") anchor := anchori.([]byte) - err := h.HeadingRenderer.RenderHeading( + err := hr.RenderHeading( w, headingContext{ page: ctx.DocumentContext().Document, @@ -417,11 +368,11 @@ func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast anchor: string(anchor), text: string(text), 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 } @@ -432,7 +383,7 @@ func (r *hookedRenderer) renderHeadingDefault(w util.BufWriter, source []byte, n _, _ = w.WriteString("') } else { diff --git a/markup/goldmark/toc_test.go b/markup/goldmark/toc_test.go index f8fcf79d4d9..6e080bf468d 100644 --- a/markup/goldmark/toc_test.go +++ b/markup/goldmark/toc_test.go @@ -18,6 +18,7 @@ import ( "strings" "testing" + "github.com/gohugoio/hugo/markup/converter/hooks" "github.com/gohugoio/hugo/markup/markup_config" "github.com/gohugoio/hugo/common/loggers" @@ -27,6 +28,8 @@ import ( qt "github.com/frankban/quicktest" ) +var nopGetRenderer = func(t hooks.RendererType, id interface{}) interface{} { return nil } + func TestToc(t *testing.T) { c := qt.New(t) @@ -58,7 +61,7 @@ And then some. c.Assert(err, qt.IsNil) conv, err := p.New(converter.DocumentContext{}) 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) got := b.(converter.TableOfContentsProvider).TableOfContents().ToHTML(2, 3, false) c.Assert(got, qt.Equals, `