shithub: hugo

Download patch

ref: 7204b354a9f46778f068a4712447d6d4fefbefd8
parent: 574c2959b8d3338764fa1db102a5e0fd6ed322d9
author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
date: Fri Mar 20 12:34:53 EDT 2020

Some minify configuration adjustments

--- a/commands/gendocshelper.go
+++ b/commands/gendocshelper.go
@@ -64,7 +64,7 @@
 	enc := json.NewEncoder(f)
 	enc.SetIndent("", "  ")
 
-	if err := enc.Encode(docshelper.DocProviders); err != nil {
+	if err := enc.Encode(docshelper.GetDocProvider()); err != nil {
 		return err
 	}
 
--- a/commands/hugo.go
+++ b/commands/hugo.go
@@ -231,11 +231,6 @@
 		"duplicateTargetPaths",
 	}
 
-	// Will set a value even if it is the default.
-	flagKeysForced := []string{
-		"minify",
-	}
-
 	for _, key := range persFlagKeys {
 		setValueFromFlag(cmd.PersistentFlags(), key, cfg, "", false)
 	}
@@ -243,9 +238,7 @@
 		setValueFromFlag(cmd.Flags(), key, cfg, "", false)
 	}
 
-	for _, key := range flagKeysForced {
-		setValueFromFlag(cmd.Flags(), key, cfg, "", true)
-	}
+	setValueFromFlag(cmd.Flags(), "minify", cfg, "minifyOutput", true)
 
 	// Set some "config aliases"
 	setValueFromFlag(cmd.Flags(), "destination", cfg, "publishDir", false)
--- a/docs/content/en/getting-started/configuration.md
+++ b/docs/content/en/getting-started/configuration.md
@@ -193,6 +193,9 @@
 menu
 : See [Add Non-content Entries to a Menu](/content-management/menus/#add-non-content-entries-to-a-menu).
 
+minify
+: See [Configure Minify](#configure-minify)
+
 module
 : Module config see [Module Config](/hugo-modules/configuration/).{{< new-in "0.56.0" >}}
 
@@ -480,6 +483,14 @@
 ## Configure Additional Output Formats
 
 Hugo v0.20 introduced the ability to render your content to multiple output formats (e.g., to JSON, AMP html, or CSV). See [Output Formats][] for information on how to add these values to your Hugo project's configuration file.
+
+## Configure Minify
+
+{{< new-in "0.68.0" >}}
+
+Default configuration:
+
+{{< code-toggle config="minify" />}}
 
 ## Configure File Caches
 
--- a/docs/data/docs.json
+++ b/docs/data/docs.json
@@ -1415,7 +1415,7 @@
       "goldmark": {
         "renderer": {
           "hardWraps": false,
-          "xHTML": false,
+          "xhtml": false,
           "unsafe": false
         },
         "parser": {
@@ -1452,14 +1452,15 @@
         "footnoteReturnLinkContents": ""
       }
     },
-    "minifiers": {
+    "minify": {
+      "minifyOutput": false,
+      "disableHTML": false,
+      "disableCSS": false,
+      "disableJS": false,
+      "disableJSON": false,
+      "disableSVG": false,
+      "disableXML": false,
       "tdewolff": {
-        "enableHtml": true,
-        "enableCss": true,
-        "enableJs": true,
-        "enableJson": true,
-        "enableSvg": true,
-        "enableXml": true,
         "html": {
           "keepConditionalComments": true,
           "keepDefaultAttrVals": true,
--- a/docshelper/docs.go
+++ b/docshelper/docs.go
@@ -15,37 +15,37 @@
 // is of limited interest for the general Hugo user.
 package docshelper
 
-import (
-	"encoding/json"
+type (
+	DocProviderFunc = func() DocProvider
+	DocProvider     map[string]map[string]interface{}
 )
 
-// DocProviders contains all DocProviders added to the system.
-var DocProviders = make(map[string]DocProvider)
+var docProviderFuncs []DocProviderFunc
 
-// AddDocProvider adds or updates the DocProvider for a given name.
-func AddDocProvider(name string, provider DocProvider) {
-	if prev, ok := DocProviders[name]; !ok {
-		DocProviders[name] = provider
-	} else {
-		DocProviders[name] = merge(prev, provider)
-	}
+func AddDocProviderFunc(fn DocProviderFunc) {
+	docProviderFuncs = append(docProviderFuncs, fn)
 }
 
-// DocProvider is used to save arbitrary JSON data
-// used for the generation of the documentation.
-type DocProvider func() map[string]interface{}
+func GetDocProvider() DocProvider {
+	provider := make(DocProvider)
 
-// MarshalJSON returns a JSON representation of the DocProvider.
-func (d DocProvider) MarshalJSON() ([]byte, error) {
-	return json.MarshalIndent(d(), "", "  ")
+	for _, fn := range docProviderFuncs {
+		p := fn()
+		for k, v := range p {
+			if prev, found := provider[k]; !found {
+				provider[k] = v
+			} else {
+				merge(prev, v)
+			}
+		}
+	}
+
+	return provider
 }
 
-func merge(a, b DocProvider) DocProvider {
-	next := a()
-	for k, v := range b() {
-		next[k] = v
-	}
-	return func() map[string]interface{} {
-		return next
+// Shallow merge
+func merge(dst, src map[string]interface{}) {
+	for k, v := range src {
+		dst[k] = v
 	}
 }
--- a/helpers/docshelper.go
+++ b/helpers/docshelper.go
@@ -12,8 +12,7 @@
 // This is is just some helpers used to create some JSON used in the Hugo docs.
 func init() {
 
-	docsProvider := func() map[string]interface{} {
-		docs := make(map[string]interface{})
+	docsProvider := func() docshelper.DocProvider {
 
 		var chromaLexers []interface{}
 
@@ -48,11 +47,11 @@
 
 			chromaLexers = append(chromaLexers, lexerEntry)
 
-			docs["lexers"] = chromaLexers
 		}
-		return docs
 
+		return docshelper.DocProvider{"chroma": map[string]interface{}{"lexers": chromaLexers}}
+
 	}
 
-	docshelper.AddDocProvider("chroma", docsProvider)
+	docshelper.AddDocProviderFunc(docsProvider)
 }
--- a/hugolib/resource_chain_test.go
+++ b/hugolib/resource_chain_test.go
@@ -947,3 +947,33 @@
 	build("never", true)
 
 }
+
+func TestResourceMinifyDisabled(t *testing.T) {
+	t.Parallel()
+
+	b := newTestSitesBuilder(t).WithConfigFile("toml", `
+baseURL = "https://example.org"
+
+[minify]
+disableXML=true
+
+
+`)
+
+	b.WithContent("page.md", "")
+
+	b.WithSourceFile(
+		"assets/xml/data.xml", "<root>   <foo> asdfasdf </foo> </root>",
+	)
+
+	b.WithTemplates("index.html", `
+{{ $xml := resources.Get "xml/data.xml" | minify | fingerprint }}
+XML: {{ $xml.Content | safeHTML }}|{{ $xml.RelPermalink }}
+`)
+
+	b.Build(BuildCfg{})
+
+	b.AssertFileContent("public/index.html", `
+XML: <root>   <foo> asdfasdf </foo> </root>|/xml/data.min.3be4fddd19aaebb18c48dd6645215b822df74701957d6d36e59f203f9c30fd9f.xml
+`)
+}
--- a/markup/markup_config/config.go
+++ b/markup/markup_config/config.go
@@ -94,11 +94,8 @@
 }
 
 func init() {
-	docsProvider := func() map[string]interface{} {
-		docs := make(map[string]interface{})
-		docs["markup"] = parser.LowerCaseCamelJSONMarshaller{Value: Default}
-		return docs
-
+	docsProvider := func() docshelper.DocProvider {
+		return docshelper.DocProvider{"config": map[string]interface{}{"markup": parser.LowerCaseCamelJSONMarshaller{Value: Default}}}
 	}
-	docshelper.AddDocProvider("config", docsProvider)
+	docshelper.AddDocProviderFunc(docsProvider)
 }
--- a/media/docshelper.go
+++ b/media/docshelper.go
@@ -6,12 +6,8 @@
 
 // This is is just some helpers used to create some JSON used in the Hugo docs.
 func init() {
-	docsProvider := func() map[string]interface{} {
-		docs := make(map[string]interface{})
-
-		docs["types"] = DefaultTypes
-		return docs
+	docsProvider := func() docshelper.DocProvider {
+		return docshelper.DocProvider{"media": map[string]interface{}{"types": DefaultTypes}}
 	}
-
-	docshelper.AddDocProvider("media", docsProvider)
+	docshelper.AddDocProviderFunc(docsProvider)
 }
--- a/minifiers/config.go
+++ b/minifiers/config.go
@@ -14,6 +14,7 @@
 package minifiers
 
 import (
+	"github.com/gohugoio/hugo/common/maps"
 	"github.com/gohugoio/hugo/config"
 	"github.com/gohugoio/hugo/docshelper"
 	"github.com/gohugoio/hugo/parser"
@@ -61,36 +62,43 @@
 	XML  xml.Minifier
 }
 
-type minifiersConfig struct {
-	EnableHTML bool
-	EnableCSS  bool
-	EnableJS   bool
-	EnableJSON bool
-	EnableSVG  bool
-	EnableXML  bool
+type minifyConfig struct {
+	// Whether to minify the published output (the HTML written to /public).
+	MinifyOutput bool
 
+	DisableHTML bool
+	DisableCSS  bool
+	DisableJS   bool
+	DisableJSON bool
+	DisableSVG  bool
+	DisableXML  bool
+
 	Tdewolff tdewolffConfig
 }
 
-var defaultConfig = minifiersConfig{
-	EnableHTML: true,
-	EnableCSS:  true,
-	EnableJS:   true,
-	EnableJSON: true,
-	EnableSVG:  true,
-	EnableXML:  true,
-
+var defaultConfig = minifyConfig{
 	Tdewolff: defaultTdewolffConfig,
 }
 
-func decodeConfig(cfg config.Provider) (conf minifiersConfig, err error) {
+func decodeConfig(cfg config.Provider) (conf minifyConfig, err error) {
 	conf = defaultConfig
 
-	m := cfg.GetStringMap("minifiers")
-	if m == nil {
+	// May be set by CLI.
+	conf.MinifyOutput = cfg.GetBool("minifyOutput")
+
+	v := cfg.Get("minify")
+	if v == nil {
 		return
 	}
 
+	// Legacy.
+	if b, ok := v.(bool); ok {
+		conf.MinifyOutput = b
+		return
+	}
+
+	m := maps.ToStringMap(v)
+
 	err = mapstructure.WeakDecode(m, &conf)
 
 	if err != nil {
@@ -101,11 +109,8 @@
 }
 
 func init() {
-	docsProvider := func() map[string]interface{} {
-		docs := make(map[string]interface{})
-		docs["minifiers"] = parser.LowerCaseCamelJSONMarshaller{Value: defaultConfig}
-		return docs
-
+	docsProvider := func() docshelper.DocProvider {
+		return docshelper.DocProvider{"config": map[string]interface{}{"minify": parser.LowerCaseCamelJSONMarshaller{Value: defaultConfig}}}
 	}
-	docshelper.AddDocProvider("config", docsProvider)
+	docshelper.AddDocProviderFunc(docsProvider)
 }
--- a/minifiers/config_test.go
+++ b/minifiers/config_test.go
@@ -14,7 +14,6 @@
 package minifiers
 
 import (
-	"fmt"
 	"testing"
 
 	"github.com/spf13/viper"
@@ -26,8 +25,8 @@
 	c := qt.New(t)
 	v := viper.New()
 
-	v.Set("minifiers", map[string]interface{}{
-		"enablexml": false,
+	v.Set("minify", map[string]interface{}{
+		"disablexml": true,
 		"tdewolff": map[string]interface{}{
 			"html": map[string]interface{}{
 				"keepwhitespace": false,
@@ -36,10 +35,11 @@
 	})
 
 	conf, err := decodeConfig(v)
-	fmt.Println(conf)
 
 	c.Assert(err, qt.IsNil)
 
+	c.Assert(conf.MinifyOutput, qt.Equals, false)
+
 	// explicitly set value
 	c.Assert(conf.Tdewolff.HTML.KeepWhitespace, qt.Equals, false)
 	// default value
@@ -47,6 +47,19 @@
 	c.Assert(conf.Tdewolff.CSS.KeepCSS2, qt.Equals, true)
 
 	// `enable` flags
-	c.Assert(conf.EnableHTML, qt.Equals, true)
-	c.Assert(conf.EnableXML, qt.Equals, false)
+	c.Assert(conf.DisableHTML, qt.Equals, false)
+	c.Assert(conf.DisableXML, qt.Equals, true)
+}
+
+func TestConfigLegacy(t *testing.T) {
+	c := qt.New(t)
+	v := viper.New()
+
+	// This was a bool < Hugo v0.58.
+	v.Set("minify", true)
+
+	conf, err := decodeConfig(v)
+	c.Assert(err, qt.IsNil)
+	c.Assert(conf.MinifyOutput, qt.Equals, true)
+
 }
--- a/minifiers/minifiers.go
+++ b/minifiers/minifiers.go
@@ -30,6 +30,9 @@
 
 // Client wraps a minifier.
 type Client struct {
+	// Whether output minification is enabled (HTML in /public)
+	MinifyOutput bool
+
 	m *minify.M
 }
 
@@ -62,30 +65,30 @@
 
 	m := minify.New()
 	if err != nil {
-		return Client{m: m}, err
+		return Client{}, err
 	}
 
 	// We use the Type definition of the media types defined in the site if found.
-	if conf.EnableCSS {
+	if !conf.DisableCSS {
 		addMinifier(m, mediaTypes, "css", &conf.Tdewolff.CSS)
 	}
-	if conf.EnableJS {
+	if !conf.DisableJS {
 		addMinifier(m, mediaTypes, "js", &conf.Tdewolff.JS)
 		m.AddRegexp(regexp.MustCompile("^(application|text)/(x-)?(java|ecma)script$"), &conf.Tdewolff.JS)
 	}
-	if conf.EnableJSON {
+	if !conf.DisableJSON {
 		addMinifier(m, mediaTypes, "json", &conf.Tdewolff.JSON)
 		m.AddRegexp(regexp.MustCompile(`^(application|text)/(x-|ld\+)?json$`), &conf.Tdewolff.JSON)
 	}
-	if conf.EnableSVG {
+	if !conf.DisableSVG {
 		addMinifier(m, mediaTypes, "svg", &conf.Tdewolff.SVG)
 	}
-	if conf.EnableXML {
+	if !conf.DisableXML {
 		addMinifier(m, mediaTypes, "xml", &conf.Tdewolff.XML)
 	}
 
 	// HTML
-	if conf.EnableHTML {
+	if !conf.DisableHTML {
 		addMinifier(m, mediaTypes, "html", &conf.Tdewolff.HTML)
 		for _, of := range outputFormats {
 			if of.IsHTML {
@@ -94,7 +97,7 @@
 		}
 	}
 
-	return Client{m: m}, nil
+	return Client{m: m, MinifyOutput: conf.MinifyOutput}, nil
 }
 
 func addMinifier(m *minify.M, mt media.Types, suffix string, min minify.Minifier) {
--- a/minifiers/minifiers_test.go
+++ b/minifiers/minifiers_test.go
@@ -75,11 +75,11 @@
 
 }
 
-func TestConfiguredMinify(t *testing.T) {
+func TestConfigureMinify(t *testing.T) {
 	c := qt.New(t)
 	v := viper.New()
-	v.Set("minifiers", map[string]interface{}{
-		"enablexml": false,
+	v.Set("minify", map[string]interface{}{
+		"disablexml": true,
 		"tdewolff": map[string]interface{}{
 			"html": map[string]interface{}{
 				"keepwhitespace": true,
--- a/output/docshelper.go
+++ b/output/docshelper.go
@@ -10,15 +10,16 @@
 
 // This is is just some helpers used to create some JSON used in the Hugo docs.
 func init() {
-	docsProvider := func() map[string]interface{} {
-		docs := make(map[string]interface{})
-
-		docs["formats"] = DefaultFormats
-		docs["layouts"] = createLayoutExamples()
-		return docs
+	docsProvider := func() docshelper.DocProvider {
+		return docshelper.DocProvider{
+			"output": map[string]interface{}{
+				"formats": DefaultFormats,
+				"layouts": createLayoutExamples(),
+			},
+		}
 	}
 
-	docshelper.AddDocProvider("output", docsProvider)
+	docshelper.AddDocProviderFunc(docsProvider)
 }
 
 func createLayoutExamples() interface{} {
--- a/parser/lowercase_camel_json.go
+++ b/parser/lowercase_camel_json.go
@@ -14,6 +14,7 @@
 package parser
 
 import (
+	"bytes"
 	"encoding/json"
 	"regexp"
 	"unicode"
@@ -35,6 +36,12 @@
 	converted := keyMatchRegex.ReplaceAllFunc(
 		marshalled,
 		func(match []byte) []byte {
+
+			// Attributes on the form XML, JSON etc.
+			if bytes.Equal(match, bytes.ToUpper(match)) {
+				return bytes.ToLower(match)
+			}
+
 			// Empty keys are valid JSON, only lowercase if we do not have an
 			// empty key.
 			if len(match) > 2 {
--- a/publisher/publisher.go
+++ b/publisher/publisher.go
@@ -68,21 +68,16 @@
 // DestinationPublisher is the default and currently only publisher in Hugo. This
 // publisher prepares and publishes an item to the defined destination, e.g. /public.
 type DestinationPublisher struct {
-	fs     afero.Fs
-	minify bool
-	min    minifiers.Client
+	fs  afero.Fs
+	min minifiers.Client
 }
 
 // NewDestinationPublisher creates a new DestinationPublisher.
 func NewDestinationPublisher(fs afero.Fs, outputFormats output.Formats, mediaTypes media.Types, cfg config.Provider) (pub DestinationPublisher, err error) {
 	pub = DestinationPublisher{fs: fs}
-	minify := cfg.GetBool("minify")
-	if minify {
-		pub.min, err = minifiers.New(mediaTypes, outputFormats, cfg)
-		if err != nil {
-			return
-		}
-		pub.minify = true
+	pub.min, err = minifiers.New(mediaTypes, outputFormats, cfg)
+	if err != nil {
+		return
 	}
 	return
 }
@@ -155,7 +150,7 @@
 
 	}
 
-	if p.minify {
+	if p.min.MinifyOutput {
 		minifyTransformer := p.min.Transformer(f.OutputFormat.MediaType)
 		if minifyTransformer != nil {
 			transformers = append(transformers, minifyTransformer)
--- a/resources/resource_transformers/minifier/minify_test.go
+++ b/resources/resource_transformers/minifier/minify_test.go
@@ -41,23 +41,3 @@
 	c.Assert(content, qt.Equals, "<h1>Hugo Rocks!</h1>")
 
 }
-
-func TestNoMinifier(t *testing.T) {
-	c := qt.New(t)
-
-	spec, _ := htesting.NewTestResourceSpec()
-	spec.Cfg.Set("minifiers.enableXML", false)
-	client, _ := New(spec)
-
-	original := "<title>   Hugo Rocks!   </title>"
-	r, err := htesting.NewResourceTransformerForSpec(spec, "hugo.xml", original)
-	c.Assert(err, qt.IsNil)
-
-	transformed, err := client.Minify(r)
-	c.Assert(err, qt.IsNil)
-
-	content, err := transformed.(resource.ContentProvider).Content()
-	// error should be ignored because general users cannot control codes under `theme`s
-	c.Assert(err, qt.IsNil)
-	c.Assert(content, qt.Equals, original)
-}
--- a/tpl/cast/docshelper.go
+++ b/tpl/cast/docshelper.go
@@ -24,8 +24,7 @@
 
 // This file provides documentation support and is randomly put into this package.
 func init() {
-	docsProvider := func() map[string]interface{} {
-		docs := make(map[string]interface{})
+	docsProvider := func() docshelper.DocProvider {
 		d := &deps.Deps{
 			Cfg:                 viper.New(),
 			Log:                 loggers.NewErrorLogger(),
@@ -41,11 +40,11 @@
 
 		}
 
-		docs["funcs"] = namespaces
-		return docs
+		return docshelper.DocProvider{"tpl": map[string]interface{}{"funcs": namespaces}}
+
 	}
 
-	docshelper.AddDocProvider("tpl", docsProvider)
+	docshelper.AddDocProviderFunc(docsProvider)
 }
 
 func newTestConfig() *viper.Viper {