From c6acc246ab10df02f1655c226a1313a3a1d9cc81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Fri, 3 Jul 2026 11:38:05 +0200 Subject: [PATCH] Add markup.rst.syntaxHighlight option Fixes #5349 --- .github/workflows/test.yml | 2 +- markup/markup_config/config.go | 15 ++++- markup/rst/convert.go | 22 +++++- markup/rst/rst_config/config.go | 35 ++++++++++ markup/rst/rst_integration_test.go | 105 +++++++++++++++++++++++++++++ 5 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 markup/rst/rst_config/config.go create mode 100644 markup/rst/rst_integration_test.go diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 510e1c2a2..cee5b6b2e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -63,7 +63,7 @@ jobs: run: go install github.com/gohugoio/gotmplfmt@latest - name: Install docutils run: | - pip install docutils + pip install docutils Pygments rst2html --version - if: matrix.os == 'ubuntu-latest' name: Install pandoc on Linux diff --git a/markup/markup_config/config.go b/markup/markup_config/config.go index d79879015..205de934e 100644 --- a/markup/markup_config/config.go +++ b/markup/markup_config/config.go @@ -14,11 +14,14 @@ package markup_config import ( + "fmt" + "github.com/gohugoio/hugo/common/hmaps" "github.com/gohugoio/hugo/config" "github.com/gohugoio/hugo/markup/asciidocext/asciidocext_config" "github.com/gohugoio/hugo/markup/goldmark/goldmark_config" "github.com/gohugoio/hugo/markup/highlight" + "github.com/gohugoio/hugo/markup/rst/rst_config" "github.com/gohugoio/hugo/markup/tableofcontents" "github.com/mitchellh/mapstructure" ) @@ -39,10 +42,19 @@ type Config struct { // Configuration for the AsciiDoc external markdown engine. AsciiDocExt asciidocext_config.Config + + // Configuration for the reStructuredText external markdown engine. + RST rst_config.Config } func (c *Config) Init() error { - return c.Goldmark.Init() + if err := c.Goldmark.Init(); err != nil { + return fmt.Errorf("goldmark: %s", err) + } + if err := c.RST.Init(); err != nil { + return fmt.Errorf("rst: %s", err) + } + return nil } func Decode(cfg config.Provider) (conf Config, err error) { @@ -119,4 +131,5 @@ var Default = Config{ Goldmark: goldmark_config.Default, AsciiDocExt: asciidocext_config.Default, + RST: rst_config.Default, } diff --git a/markup/rst/convert.go b/markup/rst/convert.go index 901e20763..546dbbbbf 100644 --- a/markup/rst/convert.go +++ b/markup/rst/convert.go @@ -17,6 +17,7 @@ package rst import ( "bytes" "fmt" + "os/exec" "runtime" "github.com/gohugoio/hugo/common/hexec" @@ -71,6 +72,11 @@ func (c *rstConverter) getRstContent(src []byte, ctx converter.DocumentContext) logger.Infoln("Rendering", ctx.DocumentName, "with", binaryName, "...") + args := []string{"--leave-comments", "--initial-header-level=2"} + if c.cfg.Conf != nil && c.cfg.MarkupConfig().RST.SyntaxHighlight != "long" { + args = append(args, "--syntax-highlight="+c.cfg.MarkupConfig().RST.SyntaxHighlight) + } + var result []byte var err error @@ -80,10 +86,9 @@ func (c *rstConverter) getRstContent(src []byte, ctx converter.DocumentContext) // handle Windows manually because it doesn't do shebangs if runtime.GOOS == "windows" { pythonBinary, _ := internal.GetPythonBinaryAndExecPath() - args := []string{binaryPath, "--leave-comments", "--initial-header-level=2"} + args = append([]string{binaryPath}, args...) result, err = internal.ExternallyRenderContent(c.cfg, ctx, src, pythonBinary, args) } else { - args := []string{"--leave-comments", "--initial-header-level=2"} result, err = internal.ExternallyRenderContent(c.cfg, ctx, src, binaryName, args) } @@ -128,3 +133,16 @@ func Supports() bool { } return hasBin } + +// SupportsPygments returns whether Pygments is (or should be) installed on this computer. +func SupportsPygments() bool { + for _, python := range []string{"python3", "python"} { + if err := exec.Command(python, "-c", "import pygments").Run(); err == nil { + return true + } + } + if htesting.SupportsAll() { + panic("Pygments not installed") + } + return false +} diff --git a/markup/rst/rst_config/config.go b/markup/rst/rst_config/config.go new file mode 100644 index 000000000..2937f8fe4 --- /dev/null +++ b/markup/rst/rst_config/config.go @@ -0,0 +1,35 @@ +// Copyright 2026 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 rst_config holds reStructuredText related configuration. +package rst_config + +import "fmt" + +// Default holds Hugo's default reStructuredText configuration. +var Default = Config{ + SyntaxHighlight: "long", +} + +// Config configures reStructuredText. +type Config struct { + // Configures Pygments syntax highlighting. Valid values are "short", "long" (default) and "none". + SyntaxHighlight string +} + +func (c *Config) Init() error { + if c.SyntaxHighlight != "short" && c.SyntaxHighlight != "long" && c.SyntaxHighlight != "none" { + return fmt.Errorf("invalid value for syntaxHighlight: %q", c.SyntaxHighlight) + } + return nil +} diff --git a/markup/rst/rst_integration_test.go b/markup/rst/rst_integration_test.go new file mode 100644 index 000000000..0946635e1 --- /dev/null +++ b/markup/rst/rst_integration_test.go @@ -0,0 +1,105 @@ +// Copyright 2026 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 rst_test + +import ( + "runtime" + "strings" + "testing" + + qt "github.com/frankban/quicktest" + + "github.com/gohugoio/hugo/hugolib" + "github.com/gohugoio/hugo/markup/rst" +) + +// --syntax-highlight, default = long. +func TestRSTSyntaxHighlight(t *testing.T) { + if !rst.Supports() { + t.Skip("rst not installed") + } + if !rst.SupportsPygments() { + t.Skip("Pygments not installed") + } + + execAllow := `'^rst2html(\.py)?$'` + if runtime.GOOS == "windows" { + execAllow = `'^python(\.exe)?$'` + } + + filesTemplate := strings.ReplaceAll(` +-- hugo.toml -- +baseURL = "https://example.org" +disableKinds = ["home", "section", "taxonomy", "term", "rss", "sitemap"] + +markup.rst.syntaxHighlight = 'SYNTAX_HIGHLIGHT' + +[security.exec] +allow = [RST_EXEC_ALLOW] + +-- layouts/page.html -- +{{ .Content }} +-- content/p.rst -- +--- +title: p +--- + +.. code:: go + + if true {} +`, "RST_EXEC_ALLOW", execAllow) + + files := strings.ReplaceAll(filesTemplate, "SYNTAX_HIGHLIGHT", "none") + b := hugolib.Test(t, files) + + b.AssertFileContent("public/p/index.html", ` +! Pygments +`) + + b.AssertFileContent("public/p/index.html", ` +if true {} +! if +`) + + files = strings.ReplaceAll(filesTemplate, "SYNTAX_HIGHLIGHT", "short") + b = hugolib.Test(t, files) + + b.AssertFileContent("public/p/index.html", ` +if +! if +`) + + files = strings.ReplaceAll(filesTemplate, "SYNTAX_HIGHLIGHT", "long") + b = hugolib.Test(t, files) + + b.AssertFileContent("public/p/index.html", ` +! if +if +`) + + // Default long. + files = strings.ReplaceAll(filesTemplate, "markup.rst.syntaxHighlight = 'SYNTAX_HIGHLIGHT'", "") + b = hugolib.Test(t, files) + + b.AssertFileContent("public/p/index.html", ` +! if +if +`) + + // Invalid. + files = strings.ReplaceAll(filesTemplate, "SYNTAX_HIGHLIGHT", "foo") + b, err := hugolib.TestE(t, files) + b.Assert(err, qt.IsNotNil) + b.Assert(err.Error(), qt.Contains, "rst: invalid value for syntaxHighlight: \"foo\"") +}