markup/highlight: Allow overriding type and code via options

Treat type and code as highlighting options in both transform.Highlight
and transform.HighlightCodeBlock. The type option overrides the language
and code overrides the code, so the two functions now share the same
options handling.

transform.Highlight's LANG argument is now optional:

	transform.Highlight CODE [LANG] [OPTIONS]

Fixes #11872
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bjørn Erik Pedersen
2026-05-21 11:46:47 +02:00
parent 4bc7caea14
commit b6133657e0
5 changed files with 152 additions and 30 deletions
+36 -7
View File
@@ -94,21 +94,50 @@ func (ns *Namespace) Emojify(s any) (template.HTML, error) {
return template.HTML(helpers.Emojify([]byte(ss))), nil
}
// Highlight returns a copy of s as an HTML string with syntax
// Highlight returns a copy of CODE as an HTML string with syntax
// highlighting applied.
func (ns *Namespace) Highlight(s any, lang string, opts ...any) (template.HTML, error) {
ss, err := cast.ToStringE(s)
//
// transform.Highlight CODE [LANG] [OPTIONS]
//
// LANG is optional; it can also be set via the type option in OPTIONS, which
// makes this work the same way as HighlightCodeBlock.
func (ns *Namespace) Highlight(s any, args ...any) (template.HTML, error) {
code, err := cast.ToStringE(s)
if err != nil {
return "", err
}
var optsv any
if len(opts) > 0 {
optsv = opts[0]
var lang string
var opts any
switch len(args) {
case 0:
case 1:
// A single argument is either OPTIONS (a map or option string) or LANG.
if _, ok := args[0].(map[string]any); ok {
opts = args[0]
} else {
var arg string
if arg, err = cast.ToStringE(args[0]); err != nil {
return "", err
}
if strings.Contains(arg, "=") || strings.Contains(arg, ",") {
opts = arg
} else {
lang = arg
}
}
case 2:
if lang, err = cast.ToStringE(args[0]); err != nil {
return "", err
}
opts = args[1]
default:
return "", errors.New("transform.Highlight: expects at most 3 arguments")
}
hl := ns.deps.ContentSpec.Converters.GetHighlighter()
highlighted, err := hl.Highlight(ss, lang, optsv)
highlighted, err := hl.Highlight(code, lang, opts)
if err != nil {
return "", err
}
@@ -103,6 +103,34 @@ disableKinds = ['page','rss','section','sitemap','taxonomy','term']
b.Assert(err.Error(), qt.Contains, "error calling highlight: invalid Highlight option: 0")
}
// transform.Highlight: LANG is optional and may be set via the type option, and
// the code option overrides CODE — consistent with transform.HighlightCodeBlock.
// See issue 11872.
func TestHighlightTypeAndCodeOptions(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
[markup.highlight]
noClasses = false
-- layouts/home.html --
lang:{{ transform.Highlight "i = 42" "go" }}
type:{{ transform.Highlight "i = 42" (dict "type" "go") }}
code:{{ transform.Highlight "" (dict "type" "go" "code" "i = 42") }}
`
b := hugolib.Test(t, files)
want := `<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">i</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">42</span></span></span></code></pre></div>`
b.AssertFileContent("public/index.html",
"lang:"+want,
"type:"+want,
"code:"+want,
)
}
// Issue #11884
func TestUnmarshalCSVLazyDecoding(t *testing.T) {
t.Parallel()