markup/goldmark: Add config options for the typographer extension

Note that the config per language part of this will be handled in #10602.

Updates #9772
This commit is contained in:
Bjørn Erik Pedersen 2023-04-12 10:15:02 +02:00
parent d01731d53c
commit 5596dc24a0
5 changed files with 139 additions and 12 deletions

View file

@ -20,6 +20,7 @@ import (
"github.com/gohugoio/hugo/identity"
"github.com/gohugoio/hugo/markup/goldmark/codeblocks"
"github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
"github.com/gohugoio/hugo/markup/goldmark/images"
"github.com/gohugoio/hugo/markup/goldmark/internal/extensions/attributes"
"github.com/gohugoio/hugo/markup/goldmark/internal/render"
@ -120,8 +121,11 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
extensions = append(extensions, extension.TaskList)
}
if cfg.Extensions.Typographer {
extensions = append(extensions, extension.Typographer)
if !cfg.Extensions.Typographer.Disable {
t := extension.NewTypographer(
extension.WithTypographicSubstitutions(toTypographicPunctuationMap(cfg.Extensions.Typographer)),
)
extensions = append(extensions, t)
}
if cfg.Extensions.DefinitionList {
@ -278,3 +282,21 @@ func (p *parserContext) TableOfContents() *tableofcontents.Fragments {
}
return nil
}
// Note: It's tempting to put this in the config package, but that doesn't work.
// TODO(bep) create upstream issue.
func toTypographicPunctuationMap(t goldmark_config.Typographer) map[extension.TypographicPunctuation][]byte {
return map[extension.TypographicPunctuation][]byte{
extension.LeftSingleQuote: []byte(t.LeftSingleQuote),
extension.RightSingleQuote: []byte(t.RightSingleQuote),
extension.LeftDoubleQuote: []byte(t.LeftDoubleQuote),
extension.RightDoubleQuote: []byte(t.RightDoubleQuote),
extension.EnDash: []byte(t.EnDash),
extension.EmDash: []byte(t.EmDash),
extension.Ellipsis: []byte(t.Ellipsis),
extension.LeftAngleQuote: []byte(t.LeftAngleQuote),
extension.RightAngleQuote: []byte(t.RightAngleQuote),
extension.Apostrophe: []byte(t.Apostrophe),
}
}

View file

@ -499,3 +499,18 @@ LINE5
c.Assert(result, qt.Contains, "<span class=\"ln\">2</span><span class=\"cl\">LINE2\n</span></span>")
})
}
func TestTypographerConfig(t *testing.T) {
c := qt.New(t)
content := `
A "quote" and 'another quote' and a "quote with a 'nested' quote" and a 'quote with a "nested" quote' and an ellipsis...
`
mconf := markup_config.Default
mconf.Goldmark.Extensions.Typographer.LeftDoubleQuote = "&laquo;"
mconf.Goldmark.Extensions.Typographer.RightDoubleQuote = "&raquo;"
b := convert(c, mconf, content)
got := string(b.Bytes())
c.Assert(got, qt.Contains, "<p>A &laquo;quote&raquo; and &lsquo;another quote&rsquo; and a &laquo;quote with a &rsquo;nested&rsquo; quote&raquo; and a &lsquo;quote with a &laquo;nested&raquo; quote&rsquo; and an ellipsis&hellip;</p>\n")
}

View file

@ -23,7 +23,19 @@ const (
// DefaultConfig holds the default Goldmark configuration.
var Default = Config{
Extensions: Extensions{
Typographer: true,
Typographer: Typographer{
Disable: false,
LeftSingleQuote: "&lsquo;",
RightSingleQuote: "&rsquo;",
LeftDoubleQuote: "&ldquo;",
RightDoubleQuote: "&rdquo;",
EnDash: "&ndash;",
EmDash: "&mdash;",
Ellipsis: "&hellip;",
LeftAngleQuote: "&laquo;",
RightAngleQuote: "&raquo;",
Apostrophe: "&rsquo;",
},
Footnote: true,
DefinitionList: true,
Table: true,
@ -54,7 +66,7 @@ type Config struct {
}
type Extensions struct {
Typographer bool
Typographer Typographer
Footnote bool
DefinitionList bool
@ -66,6 +78,33 @@ type Extensions struct {
TaskList bool
}
// Typographer holds typographer configuration.
type Typographer struct {
// Whether to disable typographer.
Disable bool
// Value used for left single quote.
LeftSingleQuote string
// Value used for right single quote.
RightSingleQuote string
// Value used for left double quote.
LeftDoubleQuote string
// Value used for right double quote.
RightDoubleQuote string
// Value used for en dash.
EnDash string
// Value used for em dash.
EmDash string
// Value used for ellipsis.
Ellipsis string
// Value used for left angle quote.
LeftAngleQuote string
// Value used for right angle quote.
RightAngleQuote string
// Value used for apostrophe.
Apostrophe string
}
type Renderer struct {
// Whether softline breaks should be rendered as '<br>'
HardWraps bool

View file

@ -62,15 +62,32 @@ func Decode(cfg config.Provider) (conf Config, err error) {
func normalizeConfig(m map[string]any) {
v, err := maps.GetNestedParam("goldmark.parser", ".", m)
if err != nil {
return
if err == nil {
vm := maps.ToStringMap(v)
// Changed from a bool in 0.81.0
if vv, found := vm["attribute"]; found {
if vvb, ok := vv.(bool); ok {
vm["attribute"] = goldmark_config.ParserAttribute{
Title: vvb,
}
}
}
}
vm := maps.ToStringMap(v)
// Changed from a bool in 0.81.0
if vv, found := vm["attribute"]; found {
if vvb, ok := vv.(bool); ok {
vm["attribute"] = goldmark_config.ParserAttribute{
Title: vvb,
// Changed from a bool in 0.112.0.
v, err = maps.GetNestedParam("goldmark.extensions", ".", m)
if err == nil {
vm := maps.ToStringMap(v)
const typographerKey = "typographer"
if vv, found := vm[typographerKey]; found {
if vvb, ok := vv.(bool); ok {
if !vvb {
vm[typographerKey] = goldmark_config.Typographer{
Disable: true,
}
} else {
delete(vm, typographerKey)
}
}
}
}

View file

@ -52,4 +52,38 @@ func TestConfig(t *testing.T) {
c.Assert(conf.AsciidocExt.Extensions[0], qt.Equals, "asciidoctor-html5s")
})
c.Run("Decode legacy typographer", func(c *qt.C) {
c.Parallel()
v := config.New()
// typographer was changed from a bool to a struct in 0.112.0.
v.Set("markup", map[string]any{
"goldmark": map[string]any{
"extensions": map[string]any{
"typographer": false,
},
},
})
conf, err := Decode(v)
c.Assert(err, qt.IsNil)
c.Assert(conf.Goldmark.Extensions.Typographer.Disable, qt.Equals, true)
v.Set("markup", map[string]any{
"goldmark": map[string]any{
"extensions": map[string]any{
"typographer": true,
},
},
})
conf, err = Decode(v)
c.Assert(err, qt.IsNil)
c.Assert(conf.Goldmark.Extensions.Typographer.Disable, qt.Equals, false)
c.Assert(conf.Goldmark.Extensions.Typographer.Ellipsis, qt.Equals, "&hellip;")
})
}