From 5596dc24a0adc8907f52886a8e035e1bcd66dd8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Wed, 12 Apr 2023 10:15:02 +0200 Subject: [PATCH] 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 --- markup/goldmark/convert.go | 26 ++++++++++++-- markup/goldmark/convert_test.go | 15 ++++++++ markup/goldmark/goldmark_config/config.go | 43 +++++++++++++++++++++-- markup/markup_config/config.go | 33 ++++++++++++----- markup/markup_config/config_test.go | 34 ++++++++++++++++++ 5 files changed, 139 insertions(+), 12 deletions(-) diff --git a/markup/goldmark/convert.go b/markup/goldmark/convert.go index 3c8dbb299..efcfb7142 100644 --- a/markup/goldmark/convert.go +++ b/markup/goldmark/convert.go @@ -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), + } + +} diff --git a/markup/goldmark/convert_test.go b/markup/goldmark/convert_test.go index 647ffce58..e92c651fc 100644 --- a/markup/goldmark/convert_test.go +++ b/markup/goldmark/convert_test.go @@ -499,3 +499,18 @@ LINE5 c.Assert(result, qt.Contains, "2LINE2\n") }) } + +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 = "«" + mconf.Goldmark.Extensions.Typographer.RightDoubleQuote = "»" + b := convert(c, mconf, content) + got := string(b.Bytes()) + + c.Assert(got, qt.Contains, "

A «quote» and ‘another quote’ and a «quote with a ’nested’ quote» and a ‘quote with a «nested» quote’ and an ellipsis…

\n") +} diff --git a/markup/goldmark/goldmark_config/config.go b/markup/goldmark/goldmark_config/config.go index ff0b6bbef..f35427ac1 100644 --- a/markup/goldmark/goldmark_config/config.go +++ b/markup/goldmark/goldmark_config/config.go @@ -23,7 +23,19 @@ const ( // DefaultConfig holds the default Goldmark configuration. var Default = Config{ Extensions: Extensions{ - Typographer: true, + Typographer: Typographer{ + Disable: false, + LeftSingleQuote: "‘", + RightSingleQuote: "’", + LeftDoubleQuote: "“", + RightDoubleQuote: "”", + EnDash: "–", + EmDash: "—", + Ellipsis: "…", + LeftAngleQuote: "«", + RightAngleQuote: "»", + Apostrophe: "’", + }, 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 '
' HardWraps bool diff --git a/markup/markup_config/config.go b/markup/markup_config/config.go index e254ba7a0..60446b9bc 100644 --- a/markup/markup_config/config.go +++ b/markup/markup_config/config.go @@ -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) + } } } } diff --git a/markup/markup_config/config_test.go b/markup/markup_config/config_test.go index a320e6912..782cedbc9 100644 --- a/markup/markup_config/config_test.go +++ b/markup/markup_config/config_test.go @@ -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, "…") + + }) + }