diff options
author | Bjørn Erik Pedersen <[email protected]> | 2023-01-04 18:24:36 +0100 |
---|---|---|
committer | Bjørn Erik Pedersen <[email protected]> | 2023-05-16 18:01:29 +0200 |
commit | 241b21b0fd34d91fccb2ce69874110dceae6f926 (patch) | |
tree | d4e0118eac7e9c42f065815447a70805f8d6ad3e /output | |
parent | 6aededf6b42011c3039f5f66487a89a8dd65e0e7 (diff) | |
download | hugo-241b21b0fd34d91fccb2ce69874110dceae6f926.tar.gz hugo-241b21b0fd34d91fccb2ce69874110dceae6f926.zip |
Create a struct with all of Hugo's config options
Primary motivation is documentation, but it will also hopefully simplify the code.
Also,
* Lower case the default output format names; this is in line with the custom ones (map keys) and how
it's treated all the places. This avoids doing `stringds.EqualFold` everywhere.
Closes #10896
Closes #10620
Diffstat (limited to 'output')
-rw-r--r-- | output/config.go | 147 | ||||
-rw-r--r-- | output/config_test.go | 98 | ||||
-rw-r--r-- | output/docshelper.go | 58 | ||||
-rw-r--r-- | output/layouts/layout.go (renamed from output/layout.go) | 62 | ||||
-rw-r--r-- | output/layouts/layout_test.go (renamed from output/layout_test.go) | 179 | ||||
-rw-r--r-- | output/outputFormat.go | 157 | ||||
-rw-r--r-- | output/outputFormat_test.go | 145 |
7 files changed, 436 insertions, 410 deletions
diff --git a/output/config.go b/output/config.go new file mode 100644 index 000000000..7b83ef9de --- /dev/null +++ b/output/config.go @@ -0,0 +1,147 @@ +// Copyright 2023 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 output + +import ( + "fmt" + "reflect" + "sort" + "strings" + + "github.com/gohugoio/hugo/common/maps" + "github.com/gohugoio/hugo/config" + "github.com/gohugoio/hugo/media" + "github.com/mitchellh/mapstructure" +) + +// OutputFormatConfig configures a single output format. +type OutputFormatConfig struct { + // The MediaType string. This must be a configured media type. + MediaType string + Format +} + +func DecodeConfig(mediaTypes media.Types, in any) (*config.ConfigNamespace[map[string]OutputFormatConfig, Formats], error) { + buildConfig := func(in any) (Formats, any, error) { + f := make(Formats, len(DefaultFormats)) + copy(f, DefaultFormats) + if in != nil { + m, err := maps.ToStringMapE(in) + if err != nil { + return nil, nil, fmt.Errorf("failed convert config to map: %s", err) + } + m = maps.CleanConfigStringMap(m) + + for k, v := range m { + found := false + for i, vv := range f { + // Both are lower case. + if k == vv.Name { + // Merge it with the existing + if err := decode(mediaTypes, v, &f[i]); err != nil { + return f, nil, err + } + found = true + } + } + if found { + continue + } + + var newOutFormat Format + newOutFormat.Name = k + if err := decode(mediaTypes, v, &newOutFormat); err != nil { + return f, nil, err + } + + // We need values for these + if newOutFormat.BaseName == "" { + newOutFormat.BaseName = "index" + } + if newOutFormat.Rel == "" { + newOutFormat.Rel = "alternate" + } + + f = append(f, newOutFormat) + + } + } + + // Also format is a map for documentation purposes. + docm := make(map[string]OutputFormatConfig, len(f)) + for _, ff := range f { + docm[ff.Name] = OutputFormatConfig{ + MediaType: ff.MediaType.Type, + Format: ff, + } + } + + sort.Sort(f) + return f, docm, nil + } + + return config.DecodeNamespace[map[string]OutputFormatConfig](in, buildConfig) +} + +func decode(mediaTypes media.Types, input any, output *Format) error { + config := &mapstructure.DecoderConfig{ + Metadata: nil, + Result: output, + WeaklyTypedInput: true, + DecodeHook: func(a reflect.Type, b reflect.Type, c any) (any, error) { + if a.Kind() == reflect.Map { + dataVal := reflect.Indirect(reflect.ValueOf(c)) + for _, key := range dataVal.MapKeys() { + keyStr, ok := key.Interface().(string) + if !ok { + // Not a string key + continue + } + if strings.EqualFold(keyStr, "mediaType") { + // If mediaType is a string, look it up and replace it + // in the map. + vv := dataVal.MapIndex(key) + vvi := vv.Interface() + + switch vviv := vvi.(type) { + case media.Type: + // OK + case string: + mediaType, found := mediaTypes.GetByType(vviv) + if !found { + return c, fmt.Errorf("media type %q not found", vviv) + } + dataVal.SetMapIndex(key, reflect.ValueOf(mediaType)) + default: + return nil, fmt.Errorf("invalid output format configuration; wrong type for media type, expected string (e.g. text/html), got %T", vvi) + } + } + } + } + return c, nil + }, + } + + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return err + } + + if err = decoder.Decode(input); err != nil { + return fmt.Errorf("failed to decode output format configuration: %w", err) + } + + return nil + +} diff --git a/output/config_test.go b/output/config_test.go new file mode 100644 index 000000000..52381c5d2 --- /dev/null +++ b/output/config_test.go @@ -0,0 +1,98 @@ +// Copyright 2023 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 output + +import ( + "testing" + + qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/media" +) + +func TestDecodeConfig(t *testing.T) { + c := qt.New(t) + + mediaTypes := media.Types{media.Builtin.JSONType, media.Builtin.XMLType} + + tests := []struct { + name string + m map[string]any + shouldError bool + assert func(t *testing.T, name string, f Formats) + }{ + { + "Redefine JSON", + map[string]any{ + "json": map[string]any{ + "baseName": "myindex", + "isPlainText": "false", + }, + }, + false, + func(t *testing.T, name string, f Formats) { + msg := qt.Commentf(name) + c.Assert(len(f), qt.Equals, len(DefaultFormats), msg) + json, _ := f.GetByName("JSON") + c.Assert(json.BaseName, qt.Equals, "myindex") + c.Assert(json.MediaType, qt.Equals, media.Builtin.JSONType) + c.Assert(json.IsPlainText, qt.Equals, false) + }, + }, + { + "Add XML format with string as mediatype", + map[string]any{ + "MYXMLFORMAT": map[string]any{ + "baseName": "myxml", + "mediaType": "application/xml", + }, + }, + false, + func(t *testing.T, name string, f Formats) { + c.Assert(len(f), qt.Equals, len(DefaultFormats)+1) + xml, found := f.GetByName("MYXMLFORMAT") + c.Assert(found, qt.Equals, true) + c.Assert(xml.BaseName, qt.Equals, "myxml") + c.Assert(xml.MediaType, qt.Equals, media.Builtin.XMLType) + + // Verify that we haven't changed the DefaultFormats slice. + json, _ := f.GetByName("JSON") + c.Assert(json.BaseName, qt.Equals, "index") + }, + }, + { + "Add format unknown mediatype", + map[string]any{ + "MYINVALID": map[string]any{ + "baseName": "mymy", + "mediaType": "application/hugo", + }, + }, + true, + func(t *testing.T, name string, f Formats) { + }, + }, + } + + for _, test := range tests { + result, err := DecodeConfig(mediaTypes, test.m) + msg := qt.Commentf(test.name) + + if test.shouldError { + c.Assert(err, qt.Not(qt.IsNil), msg) + } else { + c.Assert(err, qt.IsNil, msg) + test.assert(t, test.name, result.Config) + } + } +} diff --git a/output/docshelper.go b/output/docshelper.go index abfedd148..fa8ed1342 100644 --- a/output/docshelper.go +++ b/output/docshelper.go @@ -6,6 +6,7 @@ import ( // "fmt" "github.com/gohugoio/hugo/docshelper" + "github.com/gohugoio/hugo/output/layouts" ) // This is is just some helpers used to create some JSON used in the Hugo docs. @@ -39,44 +40,43 @@ func createLayoutExamples() any { for _, example := range []struct { name string - d LayoutDescriptor - f Format + d layouts.LayoutDescriptor }{ - // Taxonomy output.LayoutDescriptor={categories category taxonomy en false Type Section - {"Single page in \"posts\" section", LayoutDescriptor{Kind: "page", Type: "posts"}, HTMLFormat}, - {"Base template for single page in \"posts\" section", LayoutDescriptor{Baseof: true, Kind: "page", Type: "posts"}, HTMLFormat}, - {"Single page in \"posts\" section with layout set", LayoutDescriptor{Kind: "page", Type: "posts", Layout: demoLayout}, HTMLFormat}, - {"Base template for single page in \"posts\" section with layout set", LayoutDescriptor{Baseof: true, Kind: "page", Type: "posts", Layout: demoLayout}, HTMLFormat}, - {"AMP single page", LayoutDescriptor{Kind: "page", Type: "posts"}, AMPFormat}, - {"AMP single page, French language", LayoutDescriptor{Kind: "page", Type: "posts", Lang: "fr"}, AMPFormat}, + // Taxonomy layouts.LayoutDescriptor={categories category taxonomy en false Type Section + {"Single page in \"posts\" section", layouts.LayoutDescriptor{Kind: "page", Type: "posts", OutputFormatName: "html", Suffix: "html"}}, + {"Base template for single page in \"posts\" section", layouts.LayoutDescriptor{Baseof: true, Kind: "page", Type: "posts", OutputFormatName: "html", Suffix: "html"}}, + {"Single page in \"posts\" section with layout set", layouts.LayoutDescriptor{Kind: "page", Type: "posts", Layout: demoLayout, OutputFormatName: "html", Suffix: "html"}}, + {"Base template for single page in \"posts\" section with layout set", layouts.LayoutDescriptor{Baseof: true, Kind: "page", Type: "posts", Layout: demoLayout, OutputFormatName: "html", Suffix: "html"}}, + {"AMP single page", layouts.LayoutDescriptor{Kind: "page", Type: "posts", OutputFormatName: "amp", Suffix: "html"}}, + {"AMP single page, French language", layouts.LayoutDescriptor{Kind: "page", Type: "posts", Lang: "fr", OutputFormatName: "html", Suffix: "html"}}, // All section or typeless pages gets "page" as type - {"Home page", LayoutDescriptor{Kind: "home", Type: "page"}, HTMLFormat}, - {"Base template for home page", LayoutDescriptor{Baseof: true, Kind: "home", Type: "page"}, HTMLFormat}, - {"Home page with type set", LayoutDescriptor{Kind: "home", Type: demoType}, HTMLFormat}, - {"Base template for home page with type set", LayoutDescriptor{Baseof: true, Kind: "home", Type: demoType}, HTMLFormat}, - {"Home page with layout set", LayoutDescriptor{Kind: "home", Type: "page", Layout: demoLayout}, HTMLFormat}, - {"AMP home, French language", LayoutDescriptor{Kind: "home", Type: "page", Lang: "fr"}, AMPFormat}, - {"JSON home", LayoutDescriptor{Kind: "home", Type: "page"}, JSONFormat}, - {"RSS home", LayoutDescriptor{Kind: "home", Type: "page"}, RSSFormat}, - {"RSS section posts", LayoutDescriptor{Kind: "section", Type: "posts"}, RSSFormat}, - {"Taxonomy in categories", LayoutDescriptor{Kind: "taxonomy", Type: "categories", Section: "category"}, RSSFormat}, - {"Term in categories", LayoutDescriptor{Kind: "term", Type: "categories", Section: "category"}, RSSFormat}, - {"Section list for \"posts\" section", LayoutDescriptor{Kind: "section", Type: "posts", Section: "posts"}, HTMLFormat}, - {"Section list for \"posts\" section with type set to \"blog\"", LayoutDescriptor{Kind: "section", Type: "blog", Section: "posts"}, HTMLFormat}, - {"Section list for \"posts\" section with layout set to \"demoLayout\"", LayoutDescriptor{Kind: "section", Layout: demoLayout, Section: "posts"}, HTMLFormat}, + {"Home page", layouts.LayoutDescriptor{Kind: "home", Type: "page", OutputFormatName: "html", Suffix: "html"}}, + {"Base template for home page", layouts.LayoutDescriptor{Baseof: true, Kind: "home", Type: "page", OutputFormatName: "html", Suffix: "html"}}, + {"Home page with type set", layouts.LayoutDescriptor{Kind: "home", Type: demoType, OutputFormatName: "html", Suffix: "html"}}, + {"Base template for home page with type set", layouts.LayoutDescriptor{Baseof: true, Kind: "home", Type: demoType, OutputFormatName: "html", Suffix: "html"}}, + {"Home page with layout set", layouts.LayoutDescriptor{Kind: "home", Type: "page", Layout: demoLayout, OutputFormatName: "html", Suffix: "html"}}, + {"AMP home, French language", layouts.LayoutDescriptor{Kind: "home", Type: "page", Lang: "fr", OutputFormatName: "amp", Suffix: "html"}}, + {"JSON home", layouts.LayoutDescriptor{Kind: "home", Type: "page", OutputFormatName: "json", Suffix: "json"}}, + {"RSS home", layouts.LayoutDescriptor{Kind: "home", Type: "page", OutputFormatName: "rss", Suffix: "rss"}}, + {"RSS section posts", layouts.LayoutDescriptor{Kind: "section", Type: "posts", OutputFormatName: "rss", Suffix: "rss"}}, + {"Taxonomy in categories", layouts.LayoutDescriptor{Kind: "taxonomy", Type: "categories", Section: "category", OutputFormatName: "rss", Suffix: "rss"}}, + {"Term in categories", layouts.LayoutDescriptor{Kind: "term", Type: "categories", Section: "category", OutputFormatName: "rss", Suffix: "rss"}}, + {"Section list for \"posts\" section", layouts.LayoutDescriptor{Kind: "section", Type: "posts", Section: "posts", OutputFormatName: "html", Suffix: "html"}}, + {"Section list for \"posts\" section with type set to \"blog\"", layouts.LayoutDescriptor{Kind: "section", Type: "blog", Section: "posts", OutputFormatName: "html", Suffix: "html"}}, + {"Section list for \"posts\" section with layout set to \"demoLayout\"", layouts.LayoutDescriptor{Kind: "section", Layout: demoLayout, Section: "posts", OutputFormatName: "html", Suffix: "html"}}, - {"Taxonomy list in categories", LayoutDescriptor{Kind: "taxonomy", Type: "categories", Section: "category"}, HTMLFormat}, - {"Taxonomy term in categories", LayoutDescriptor{Kind: "term", Type: "categories", Section: "category"}, HTMLFormat}, + {"Taxonomy list in categories", layouts.LayoutDescriptor{Kind: "taxonomy", Type: "categories", Section: "category", OutputFormatName: "html", Suffix: "html"}}, + {"Taxonomy term in categories", layouts.LayoutDescriptor{Kind: "term", Type: "categories", Section: "category", OutputFormatName: "html", Suffix: "html"}}, } { - l := NewLayoutHandler() - layouts, _ := l.For(example.d, example.f) + l := layouts.NewLayoutHandler() + layouts, _ := l.For(example.d) basicExamples = append(basicExamples, Example{ Example: example.name, Kind: example.d.Kind, - OutputFormat: example.f.Name, - Suffix: example.f.MediaType.FirstSuffix.Suffix, + OutputFormat: example.d.OutputFormatName, + Suffix: example.d.Suffix, Layouts: makeLayoutsPresentable(layouts), }) } diff --git a/output/layout.go b/output/layouts/layout.go index dcbdf461a..9c5ef17a1 100644 --- a/output/layout.go +++ b/output/layouts/layout.go @@ -1,4 +1,4 @@ -// Copyright 2017-present The Hugo Authors. All rights reserved. +// Copyright 2023 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. @@ -11,13 +11,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -package output +package layouts import ( "strings" "sync" - - "github.com/gohugoio/hugo/helpers" ) // These may be used as content sections with potential conflicts. Avoid that. @@ -43,6 +41,10 @@ type LayoutDescriptor struct { // LayoutOverride indicates what we should only look for the above layout. LayoutOverride bool + // From OutputFormat and MediaType. + OutputFormatName string + Suffix string + RenderingHook bool Baseof bool } @@ -54,37 +56,31 @@ func (d LayoutDescriptor) isList() bool { // LayoutHandler calculates the layout template to use to render a given output type. type LayoutHandler struct { mu sync.RWMutex - cache map[layoutCacheKey][]string -} - -type layoutCacheKey struct { - d LayoutDescriptor - f string + cache map[LayoutDescriptor][]string } // NewLayoutHandler creates a new LayoutHandler. func NewLayoutHandler() *LayoutHandler { - return &LayoutHandler{cache: make(map[layoutCacheKey][]string)} + return &LayoutHandler{cache: make(map[LayoutDescriptor][]string)} } // For returns a layout for the given LayoutDescriptor and options. // Layouts are rendered and cached internally. -func (l *LayoutHandler) For(d LayoutDescriptor, f Format) ([]string, error) { +func (l *LayoutHandler) For(d LayoutDescriptor) ([]string, error) { // We will get lots of requests for the same layouts, so avoid recalculations. - key := layoutCacheKey{d, f.Name} l.mu.RLock() - if cacheVal, found := l.cache[key]; found { + if cacheVal, found := l.cache[d]; found { l.mu.RUnlock() return cacheVal, nil } l.mu.RUnlock() - layouts := resolvePageTemplate(d, f) + layouts := resolvePageTemplate(d) - layouts = helpers.UniqueStringsReuse(layouts) + layouts = uniqueStringsReuse(layouts) l.mu.Lock() - l.cache[key] = layouts + l.cache[d] = layouts l.mu.Unlock() return layouts, nil @@ -94,7 +90,7 @@ type layoutBuilder struct { layoutVariations []string typeVariations []string d LayoutDescriptor - f Format + //f Format } func (l *layoutBuilder) addLayoutVariations(vars ...string) { @@ -134,8 +130,8 @@ func (l *layoutBuilder) addKind() { const renderingHookRoot = "/_markup" -func resolvePageTemplate(d LayoutDescriptor, f Format) []string { - b := &layoutBuilder{d: d, f: f} +func resolvePageTemplate(d LayoutDescriptor) []string { + b := &layoutBuilder{d: d} if !d.RenderingHook && d.Layout != "" { b.addLayoutVariations(d.Layout) @@ -190,7 +186,7 @@ func resolvePageTemplate(d LayoutDescriptor, f Format) []string { b.addTypeVariations("") } - isRSS := f.Name == RSSFormat.Name + isRSS := strings.EqualFold(d.OutputFormatName, "rss") if !d.RenderingHook && !d.Baseof && isRSS { // The historic and common rss.xml case b.addLayoutVariations("") @@ -223,7 +219,7 @@ func (l *layoutBuilder) resolveVariations() []string { var layouts []string var variations []string - name := strings.ToLower(l.f.Name) + name := strings.ToLower(l.d.OutputFormatName) if l.d.Lang != "" { // We prefer the most specific type before language. @@ -241,7 +237,7 @@ func (l *layoutBuilder) resolveVariations() []string { continue } - s := constructLayoutPath(typeVar, layoutVar, variation, l.f.MediaType.FirstSuffix.Suffix) + s := constructLayoutPath(typeVar, layoutVar, variation, l.d.Suffix) if s != "" { layouts = append(layouts, s) } @@ -300,3 +296,23 @@ func constructLayoutPath(typ, layout, variations, extension string) string { return p.String() } + +// Inline this here so we can use tinygo to compile a wasm binary of this package. +func uniqueStringsReuse(s []string) []string { + result := s[:0] + for i, val := range s { + var seen bool + + for j := 0; j < i; j++ { + if s[j] == val { + seen = true + break + } + } + + if !seen { + result = append(result, val) + } + } + return result +} diff --git a/output/layout_test.go b/output/layouts/layout_test.go index 8b7a2b541..2f340f238 100644 --- a/output/layout_test.go +++ b/output/layouts/layout_test.go @@ -11,7 +11,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package output +package layouts import ( "fmt" @@ -19,8 +19,6 @@ import ( "strings" "testing" - "github.com/gohugoio/hugo/media" - qt "github.com/frankban/quicktest" "github.com/kylelemons/godebug/diff" ) @@ -28,42 +26,16 @@ import ( func TestLayout(t *testing.T) { c := qt.New(t) - noExtNoDelimMediaType := media.WithDelimiterAndSuffixes(media.TextType, "", "") - noExtMediaType := media.WithDelimiterAndSuffixes(media.TextType, ".", "") - - var ( - ampType = Format{ - Name: "AMP", - MediaType: media.HTMLType, - BaseName: "index", - } - - htmlFormat = HTMLFormat - - noExtDelimFormat = Format{ - Name: "NEM", - MediaType: noExtNoDelimMediaType, - BaseName: "_redirects", - } - - noExt = Format{ - Name: "NEX", - MediaType: noExtMediaType, - BaseName: "next", - } - ) - for _, this := range []struct { name string layoutDescriptor LayoutDescriptor layoutOverride string - format Format expect []string }{ { "Home", - LayoutDescriptor{Kind: "home"}, - "", ampType, + LayoutDescriptor{Kind: "home", OutputFormatName: "amp", Suffix: "html"}, + "", []string{ "index.amp.html", "home.amp.html", @@ -81,8 +53,8 @@ func TestLayout(t *testing.T) { }, { "Home baseof", - LayoutDescriptor{Kind: "home", Baseof: true}, - "", ampType, + LayoutDescriptor{Kind: "home", Baseof: true, OutputFormatName: "amp", Suffix: "html"}, + "", []string{ "index-baseof.amp.html", "home-baseof.amp.html", @@ -104,8 +76,8 @@ func TestLayout(t *testing.T) { }, { "Home, HTML", - LayoutDescriptor{Kind: "home"}, - "", htmlFormat, + LayoutDescriptor{Kind: "home", OutputFormatName: "html", Suffix: "html"}, + "", // We will eventually get to index.html. This looks stuttery, but makes the lookup logic easy to understand. []string{ "index.html.html", @@ -124,8 +96,8 @@ func TestLayout(t *testing.T) { }, { "Home, HTML, baseof", - LayoutDescriptor{Kind: "home", Baseof: true}, - "", htmlFormat, + LayoutDescriptor{Kind: "home", Baseof: true, OutputFormatName: "html", Suffix: "html"}, + "", []string{ "index-baseof.html.html", "home-baseof.html.html", @@ -147,8 +119,8 @@ func TestLayout(t *testing.T) { }, { "Home, french language", - LayoutDescriptor{Kind: "home", Lang: "fr"}, - "", ampType, + LayoutDescriptor{Kind: "home", Lang: "fr", OutputFormatName: "amp", Suffix: "html"}, + "", []string{ "index.fr.amp.html", "home.fr.amp.html", @@ -178,8 +150,8 @@ func TestLayout(t *testing.T) { }, { "Home, no ext or delim", - LayoutDescriptor{Kind: "home"}, - "", noExtDelimFormat, + LayoutDescriptor{Kind: "home", OutputFormatName: "nem", Suffix: ""}, + "", []string{ "index.nem", "home.nem", @@ -191,8 +163,8 @@ func TestLayout(t *testing.T) { }, { "Home, no ext", - LayoutDescriptor{Kind: "home"}, - "", noExt, + LayoutDescriptor{Kind: "home", OutputFormatName: "nex", Suffix: ""}, + "", []string{ "index.nex", "home.nex", @@ -204,14 +176,14 @@ func TestLayout(t *testing.T) { }, { "Page, no ext or delim", - LayoutDescriptor{Kind: "page"}, - "", noExtDelimFormat, + LayoutDescriptor{Kind: "page", OutputFormatName: "nem", Suffix: ""}, + "", []string{"_default/single.nem"}, }, { "Section", - LayoutDescriptor{Kind: "section", Section: "sect1"}, - "", ampType, + LayoutDescriptor{Kind: "section", Section: "sect1", OutputFormatName: "amp", Suffix: "html"}, + "", []string{ "sect1/sect1.amp.html", "sect1/section.amp.html", @@ -235,8 +207,8 @@ func TestLayout(t *testing.T) { }, { "Section, baseof", - LayoutDescriptor{Kind: "section", Section: "sect1", Baseof: true}, - "", ampType, + LayoutDescriptor{Kind: "section", Section: "sect1", Baseof: true, OutputFormatName: "amp", Suffix: "html"}, + "", []string{ "sect1/sect1-baseof.amp.html", "sect1/section-baseof.amp.html", @@ -266,8 +238,8 @@ func TestLayout(t *testing.T) { }, { "Section, baseof, French, AMP", - LayoutDescriptor{Kind: "section", Section: "sect1", Lang: "fr", Baseof: true}, - "", ampType, + LayoutDescriptor{Kind: "section", Section: "sect1", Lang: "fr", Baseof: true, OutputFormatName: "amp", Suffix: "html"}, + "", []string{ "sect1/sect1-baseof.fr.amp.html", "sect1/section-baseof.fr.amp.html", @@ -321,8 +293,8 @@ func TestLayout(t *testing.T) { }, { "Section with layout", - LayoutDescriptor{Kind: "section", Section: "sect1", Layout: "mylayout"}, - "", ampType, + LayoutDescriptor{Kind: "section", Section: "sect1", Layout: "mylayout", OutputFormatName: "amp", Suffix: "html"}, + "", []string{ "sect1/mylayout.amp.html", "sect1/sect1.amp.html", @@ -352,8 +324,8 @@ func TestLayout(t *testing.T) { }, { "Term, French, AMP", - LayoutDescriptor{Kind: "term", Section: "tags", Lang: "fr"}, - "", ampType, + LayoutDescriptor{Kind: "term", Section: "tags", Lang: "fr", OutputFormatName: "amp", Suffix: "html"}, + "", []string{ "term/term.fr.amp.html", "term/tags.fr.amp.html", @@ -423,8 +395,8 @@ func TestLayout(t *testing.T) { }, { "Term, baseof, French, AMP", - LayoutDescriptor{Kind: "term", Section: "tags", Lang: "fr", Baseof: true}, - "", ampType, + LayoutDescriptor{Kind: "term", Section: "tags", Lang: "fr", Baseof: true, OutputFormatName: "amp", Suffix: "html"}, + "", []string{ "term/term-baseof.fr.amp.html", "term/tags-baseof.fr.amp.html", @@ -510,8 +482,8 @@ func TestLayout(t *testing.T) { }, { "Term", - LayoutDescriptor{Kind: "term", Section: "tags"}, - "", ampType, + LayoutDescriptor{Kind: "term", Section: "tags", OutputFormatName: "amp", Suffix: "html"}, + "", []string{ "term/term.amp.html", "term/tags.amp.html", @@ -549,8 +521,8 @@ func TestLayout(t *testing.T) { }, { "Taxonomy", - LayoutDescriptor{Kind: "taxonomy", Section: "categories"}, - "", ampType, + LayoutDescriptor{Kind: "taxonomy", Section: "categories", OutputFormatName: "amp", Suffix: "html"}, + "", []string{ "categories/categories.terms.amp.html", "categories/terms.amp.html", @@ -580,8 +552,8 @@ func TestLayout(t *testing.T) { }, { "Page", - LayoutDescriptor{Kind: "page"}, - "", ampType, + LayoutDescriptor{Kind: "page", OutputFormatName: "amp", Suffix: "html"}, + "", []string{ "_default/single.amp.html", "_default/single.html", @@ -589,8 +561,8 @@ func TestLayout(t *testing.T) { }, { "Page, baseof", - LayoutDescriptor{Kind: "page", Baseof: true}, - "", ampType, + LayoutDescriptor{Kind: "page", Baseof: true, OutputFormatName: "amp", Suffix: "html"}, + "", []string{ "_default/single-baseof.amp.html", "_default/baseof.amp.html", @@ -600,8 +572,8 @@ func TestLayout(t *testing.T) { }, { "Page with layout", - LayoutDescriptor{Kind: "page", Layout: "mylayout"}, - "", ampType, + LayoutDescriptor{Kind: "page", Layout: "mylayout", OutputFormatName: "amp", Suffix: "html"}, + "", []string{ "_default/mylayout.amp.html", "_default/single.amp.html", @@ -611,8 +583,8 @@ func TestLayout(t *testing.T) { }, { "Page with layout, baseof", - LayoutDescriptor{Kind: "page", Layout: "mylayout", Baseof: true}, - "", ampType, + LayoutDescriptor{Kind: "page", Layout: "mylayout", Baseof: true, OutputFormatName: "amp", Suffix: "html"}, + "", []string{ "_default/mylayout-baseof.amp.html", "_default/single-baseof.amp.html", @@ -624,8 +596,8 @@ func TestLayout(t *testing.T) { }, { "Page with layout and type", - LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype"}, - "", ampType, + LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype", OutputFormatName: "amp", Suffix: "html"}, + "", []string{ "myttype/mylayout.amp.html", "myttype/single.amp.html", @@ -639,8 +611,8 @@ func TestLayout(t *testing.T) { }, { "Page baseof with layout and type", - LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype", Baseof: true}, - "", ampType, + LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype", Baseof: true, OutputFormatName: "amp", Suffix: "html"}, + "", []string{ "myttype/mylayout-baseof.amp.html", "myttype/single-baseof.amp.html", @@ -658,8 +630,8 @@ func TestLayout(t *testing.T) { }, { "Page baseof with layout and type in French", - LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype", Lang: "fr", Baseof: true}, - "", ampType, + LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype", Lang: "fr", Baseof: true, OutputFormatName: "amp", Suffix: "html"}, + "", []string{ "myttype/mylayout-baseof.fr.amp.html", "myttype/single-baseof.fr.amp.html", @@ -689,8 +661,8 @@ func TestLayout(t *testing.T) { }, { "Page with layout and type with subtype", - LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype/mysubtype"}, - "", ampType, + LayoutDescriptor{Kind: "page", Layout: "mylayout", Type: "myttype/mysubtype", OutputFormatName: "amp", Suffix: "html"}, + "", []string{ "myttype/mysubtype/mylayout.amp.html", "myttype/mysubtype/single.amp.html", @@ -705,8 +677,8 @@ func TestLayout(t *testing.T) { // RSS { "RSS Home", - LayoutDescriptor{Kind: "home"}, - "", RSSFormat, + LayoutDescriptor{Kind: "home", OutputFormatName: "rss", Suffix: "xml"}, + "", []string{ "index.rss.xml", "home.rss.xml", @@ -727,8 +699,8 @@ func TestLayout(t *testing.T) { }, { "RSS Home, baseof", - LayoutDescriptor{Kind: "home", Baseof: true}, - "", RSSFormat, + LayoutDescriptor{Kind: "home", Baseof: true, OutputFormatName: "rss", Suffix: "xml"}, + "", []string{ "index-baseof.rss.xml", "home-baseof.rss.xml", @@ -750,8 +722,8 @@ func TestLayout(t *testing.T) { }, { "RSS Section", - LayoutDescriptor{Kind: "section", Section: "sect1"}, - "", RSSFormat, + LayoutDescriptor{Kind: "section", Section: "sect1", OutputFormatName: "rss", Suffix: "xml"}, + "", []string{ "sect1/sect1.rss.xml", "sect1/section.rss.xml", @@ -779,8 +751,8 @@ func TestLayout(t *testing.T) { }, { "RSS Term", - LayoutDescriptor{Kind: "term", Section: "tag"}, - "", RSSFormat, + LayoutDescriptor{Kind: "term", Section: "tag", OutputFormatName: "rss", Suffix: "xml"}, + "", []string{ "term/term.rss.xml", "term/tag.rss.xml", @@ -823,8 +795,8 @@ func TestLayout(t *testing.T) { }, { "RSS Taxonomy", - LayoutDescriptor{Kind: "taxonomy", Section: "tag"}, - "", RSSFormat, + LayoutDescriptor{Kind: "taxonomy", Section: "tag", OutputFormatName: "rss", Suffix: "xml"}, + "", []string{ "tag/tag.terms.rss.xml", "tag/terms.rss.xml", @@ -858,8 +830,8 @@ func TestLayout(t *testing.T) { }, { "Home plain text", - LayoutDescriptor{Kind: "home"}, - "", JSONFormat, + LayoutDescriptor{Kind: "home", OutputFormatName: "json", Suffix: "json"}, + "", []string{ "index.json.json", "home.json.json", @@ -877,8 +849,8 @@ func TestLayout(t *testing.T) { }, { "Page plain text", - LayoutDescriptor{Kind: "page"}, - "", JSONFormat, + LayoutDescriptor{Kind: "page", OutputFormatName: "json", Suffix: "json"}, + "", []string{ "_default/single.json.json", "_default/single.json", @@ -886,8 +858,8 @@ func TestLayout(t *testing.T) { }, { "Reserved section, shortcodes", - LayoutDescriptor{Kind: "section", Section: "shortcodes", Type: "shortcodes"}, - "", ampType, + LayoutDescriptor{Kind: "section", Section: "shortcodes", Type: "shortcodes", OutputFormatName: "amp", Suffix: "html"}, + "", []string{ "section/shortcodes.amp.html", "section/section.amp.html", @@ -905,8 +877,8 @@ func TestLayout(t *testing.T) { }, { "Reserved section, partials", - LayoutDescriptor{Kind: "section", Section: "partials", Type: "partials"}, - "", ampType, + LayoutDescriptor{Kind: "section", Section: "partials", Type: "partials", OutputFormatName: "amp", Suffix: "html"}, + "", []string{ "section/partials.amp.html", "section/section.amp.html", @@ -925,8 +897,8 @@ func TestLayout(t *testing.T) { // This is currently always HTML only { "404, HTML", - LayoutDescriptor{Kind: "404"}, - "", htmlFormat, + LayoutDescriptor{Kind: "404", OutputFormatName: "html", Suffix: "html"}, + "", []string{ "404.html.html", "404.html", @@ -934,8 +906,8 @@ func TestLayout(t *testing.T) { }, { "404, HTML baseof", - LayoutDescriptor{Kind: "404", Baseof: true}, - "", htmlFormat, + LayoutDescriptor{Kind: "404", Baseof: true, OutputFormatName: "html", Suffix: "html"}, + "", []string{ "404-baseof.html.html", "baseof.html.html", @@ -949,8 +921,8 @@ func TestLayout(t *testing.T) { }, { "Content hook", - LayoutDescriptor{Kind: "render-link", RenderingHook: true, Layout: "mylayout", Section: "blog"}, - "", ampType, + LayoutDescriptor{Kind: "render-link", RenderingHook: true, Layout: "mylayout", Section: "blog", OutputFormatName: "amp", Suffix: "html"}, + "", []string{ "blog/_markup/render-link.amp.html", "blog/_markup/render-link.html", @@ -962,7 +934,7 @@ func TestLayout(t *testing.T) { c.Run(this.name, func(c *qt.C) { l := NewLayoutHandler() - layouts, err := l.For(this.layoutDescriptor, this.format) + layouts, err := l.For(this.layoutDescriptor) c.Assert(err, qt.IsNil) c.Assert(layouts, qt.Not(qt.IsNil), qt.Commentf(this.layoutDescriptor.Kind)) @@ -981,8 +953,10 @@ func TestLayout(t *testing.T) { } }) } + } +/* func BenchmarkLayout(b *testing.B) { descriptor := LayoutDescriptor{Kind: "taxonomy", Section: "categories"} l := NewLayoutHandler() @@ -1006,3 +980,4 @@ func BenchmarkLayoutUncached(b *testing.B) { } } } +*/ diff --git a/output/outputFormat.go b/output/outputFormat.go index 0bc08e490..f602c03f3 100644 --- a/output/outputFormat.go +++ b/output/outputFormat.go @@ -17,19 +17,18 @@ package output import ( "encoding/json" "fmt" - "reflect" "sort" "strings" - "github.com/mitchellh/mapstructure" - "github.com/gohugoio/hugo/media" ) // Format represents an output representation, usually to a file on disk. +// <docsmeta>{ "name": "OutputFormat" }</docsmeta> type Format struct { - // The Name is used as an identifier. Internal output formats (i.e. HTML and RSS) + // The Name is used as an identifier. Internal output formats (i.e. html and rss) // can be overridden by providing a new definition for those types. + // <docsmeta>{ "identifiers": ["html", "rss"] }</docsmeta> Name string `json:"name"` MediaType media.Type `json:"-"` @@ -40,14 +39,7 @@ type Format struct { // The base output file name used when not using "ugly URLs", defaults to "index". BaseName string `json:"baseName"` - // The value to use for rel links - // - // See https://www.w3schools.com/tags/att_link_rel.asp - // - // AMP has a special requirement in this department, see: - // https://www.ampproject.org/docs/guides/deploy/discovery - // I.e.: - // <link rel="amphtml" href="https://www.example.com/url/to/amp/document.html"> + // The value to use for rel links. Rel string `json:"rel"` // The protocol to use, i.e. "webcal://". Defaults to the protocol of the baseURL. @@ -86,8 +78,8 @@ type Format struct { // An ordered list of built-in output formats. var ( AMPFormat = Format{ - Name: "AMP", - MediaType: media.HTMLType, + Name: "amp", + MediaType: media.Builtin.HTMLType, BaseName: "index", Path: "amp", Rel: "amphtml", @@ -97,8 +89,8 @@ var ( } CalendarFormat = Format{ - Name: "Calendar", - MediaType: media.CalendarType, + Name: "calendar", + MediaType: media.Builtin.CalendarType, IsPlainText: true, Protocol: "webcal://", BaseName: "index", @@ -106,24 +98,24 @@ var ( } CSSFormat = Format{ - Name: "CSS", - MediaType: media.CSSType, + Name: "css", + MediaType: media.Builtin.CSSType, BaseName: "styles", IsPlainText: true, Rel: "stylesheet", NotAlternative: true, } CSVFormat = Format{ - Name: "CSV", - MediaType: media.CSVType, + Name: "csv", + MediaType: media.Builtin.CSVType, BaseName: "index", IsPlainText: true, Rel: "alternate", } HTMLFormat = Format{ - Name: "HTML", - MediaType: media.HTMLType, + Name: "html", + MediaType: media.Builtin.HTMLType, BaseName: "index", Rel: "canonical", IsHTML: true, @@ -135,24 +127,24 @@ var ( } MarkdownFormat = Format{ - Name: "MARKDOWN", - MediaType: media.MarkdownType, + Name: "markdown", + MediaType: media.Builtin.MarkdownType, BaseName: "index", Rel: "alternate", IsPlainText: true, } JSONFormat = Format{ - Name: "JSON", - MediaType: media.JSONType, + Name: "json", + MediaType: media.Builtin.JSONType, BaseName: "index", IsPlainText: true, Rel: "alternate", } WebAppManifestFormat = Format{ - Name: "WebAppManifest", - MediaType: media.WebAppManifestType, + Name: "webappmanifest", + MediaType: media.Builtin.WebAppManifestType, BaseName: "manifest", IsPlainText: true, NotAlternative: true, @@ -160,24 +152,24 @@ var ( } RobotsTxtFormat = Format{ - Name: "ROBOTS", - MediaType: media.TextType, + Name: "robots", + MediaType: media.Builtin.TextType, BaseName: "robots", IsPlainText: true, Rel: "alternate", } RSSFormat = Format{ - Name: "RSS", - MediaType: media.RSSType, + Name: "rss", + MediaType: media.Builtin.RSSType, BaseName: "index", NoUgly: true, Rel: "alternate", } SitemapFormat = Format{ - Name: "Sitemap", - MediaType: media.XMLType, + Name: "sitemap", + MediaType: media.Builtin.XMLType, BaseName: "sitemap", NoUgly: true, Rel: "sitemap", @@ -204,6 +196,7 @@ func init() { } // Formats is a slice of Format. +// <docsmeta>{ "name": "OutputFormats" }</docsmeta> type Formats []Format func (formats Formats) Len() int { return len(formats) } @@ -298,102 +291,6 @@ func (formats Formats) FromFilename(filename string) (f Format, found bool) { return } -// DecodeFormats takes a list of output format configurations and merges those, -// in the order given, with the Hugo defaults as the last resort. -func DecodeFormats(mediaTypes media.Types, maps ...map[string]any) (Formats, error) { - f := make(Formats, len(DefaultFormats)) - copy(f, DefaultFormats) - - for _, m := range maps { - for k, v := range m { - found := false - for i, vv := range f { - if strings.EqualFold(k, vv.Name) { - // Merge it with the existing - if err := decode(mediaTypes, v, &f[i]); err != nil { - return f, err - } - found = true - } - } - if !found { - var newOutFormat Format - newOutFormat.Name = k - if err := decode(mediaTypes, v, &newOutFormat); err != nil { - return f, err - } - - // We need values for these - if newOutFormat.BaseName == "" { - newOutFormat.BaseName = "index" - } - if newOutFormat.Rel == "" { - newOutFormat.Rel = "alternate" - } - - f = append(f, newOutFormat) - - } - } - } - - sort.Sort(f) - - return f, nil -} - -func decode(mediaTypes media.Types, input any, output *Format) error { - config := &mapstructure.DecoderConfig{ - Metadata: nil, - Result: output, - WeaklyTypedInput: true, - DecodeHook: func(a reflect.Type, b reflect.Type, c any) (any, error) { - if a.Kind() == reflect.Map { - dataVal := reflect.Indirect(reflect.ValueOf(c)) - for _, key := range dataVal.MapKeys() { - keyStr, ok := key.Interface().(string) - if !ok { - // Not a string key - continue - } - if strings.EqualFold(keyStr, "mediaType") { - // If mediaType is a string, look it up and replace it - // in the map. - vv := dataVal.MapIndex(key) - vvi := vv.Interface() - - switch vviv := vvi.(type) { - case media.Type: - // OK - case string: - mediaType, found := mediaTypes.GetByType(vviv) - if !found { - return c, fmt.Errorf("media type %q not found", vviv) - } - dataVal.SetMapIndex(key, reflect.ValueOf(mediaType)) - default: - return nil, fmt.Errorf("invalid output format configuration; wrong type for media type, expected string (e.g. text/html), got %T", vvi) - } - } - } - } - return c, nil - }, - } - - decoder, err := mapstructure.NewDecoder(config) - if err != nil { - return err - } - - if err = decoder.Decode(input); err != nil { - return fmt.Errorf("failed to decode output format configuration: %w", err) - } - - return nil - -} - // BaseFilename returns the base filename of f including an extension (ie. // "index.xml"). func (f Format) BaseFilename() string { diff --git a/output/outputFormat_test.go b/output/outputFormat_test.go index c5c4534bf..13e24af3b 100644 --- a/output/outputFormat_test.go +++ b/output/outputFormat_test.go @@ -23,46 +23,46 @@ import ( func TestDefaultTypes(t *testing.T) { c := qt.New(t) - c.Assert(CalendarFormat.Name, qt.Equals, "Calendar") - c.Assert(CalendarFormat.MediaType, qt.Equals, media.CalendarType) + c.Assert(CalendarFormat.Name, qt.Equals, "calendar") + c.Assert(CalendarFormat.MediaType, qt.Equals, media.Builtin.CalendarType) c.Assert(CalendarFormat.Protocol, qt.Equals, "webcal://") c.Assert(CalendarFormat.Path, qt.HasLen, 0) c.Assert(CalendarFormat.IsPlainText, qt.Equals, true) c.Assert(CalendarFormat.IsHTML, qt.Equals, false) - c.Assert(CSSFormat.Name, qt.Equals, "CSS") - c.Assert(CSSFormat.MediaType, qt.Equals, media.CSSType) + c.Assert(CSSFormat.Name, qt.Equals, "css") + c.Assert(CSSFormat.MediaType, qt.Equals, media.Builtin.CSSType) c.Assert(CSSFormat.Path, qt.HasLen, 0) c.Assert(CSSFormat.Protocol, qt.HasLen, 0) // Will inherit the BaseURL protocol. c.Assert(CSSFormat.IsPlainText, qt.Equals, true) c.Assert(CSSFormat.IsHTML, qt.Equals, false) - c.Assert(CSVFormat.Name, qt.Equals, "CSV") - c.Assert(CSVFormat.MediaType, qt.Equals, media.CSVType) + c.Assert(CSVFormat.Name, qt.Equals, "csv") + c.Assert(CSVFormat.MediaType, qt.Equals, media.Builtin.CSVType) c.Assert(CSVFormat.Path, qt.HasLen, 0) c.Assert(CSVFormat.Protocol, qt.HasLen, 0) c.Assert(CSVFormat.IsPlainText, qt.Equals, true) c.Assert(CSVFormat.IsHTML, qt.Equals, false) c.Assert(CSVFormat.Permalinkable, qt.Equals, false) - c.Assert(HTMLFormat.Name, qt.Equals, "HTML") - c.Assert(HTMLFormat.MediaType, qt.Equals, media.HTMLType) + c.Assert(HTMLFormat.Name, qt.Equals, "html") + c.Assert(HTMLFormat.MediaType, qt.Equals, media.Builtin.HTMLType) c.Assert(HTMLFormat.Path, qt.HasLen, 0) c.Assert(HTMLFormat.Protocol, qt.HasLen, 0) c.Assert(HTMLFormat.IsPlainText, qt.Equals, false) c.Assert(HTMLFormat.IsHTML, qt.Equals, true) c.Assert(AMPFormat.Permalinkable, qt.Equals, true) - c.Assert(AMPFormat.Name, qt.Equals, "AMP") - c.Assert(AMPFormat.MediaType, qt.Equals, media.HTMLType) + c.Assert(AMPFormat.Name, qt.Equals, "amp") + c.Assert(AMPFormat.MediaType, qt.Equals, media.Builtin.HTMLType) c.Assert(AMPFormat.Path, qt.Equals, "amp") c.Assert(AMPFormat.Protocol, qt.HasLen, 0) c.Assert(AMPFormat.IsPlainText, qt.Equals, false) c.Assert(AMPFormat.IsHTML, qt.Equals, true) c.Assert(AMPFormat.Permalinkable, qt.Equals, true) - c.Assert(RSSFormat.Name, qt.Equals, "RSS") - c.Assert(RSSFormat.MediaType, qt.Equals, media.RSSType) + c.Assert(RSSFormat.Name, qt.Equals, "rss") + c.Assert(RSSFormat.MediaType, qt.Equals, media.Builtin.RSSType) c.Assert(RSSFormat.Path, qt.HasLen, 0) c.Assert(RSSFormat.IsPlainText, qt.Equals, false) c.Assert(RSSFormat.NoUgly, qt.Equals, true) @@ -101,10 +101,10 @@ func TestGetFormatByExt(t *testing.T) { func TestGetFormatByFilename(t *testing.T) { c := qt.New(t) - noExtNoDelimMediaType := media.TextType + noExtNoDelimMediaType := media.Builtin.TextType noExtNoDelimMediaType.Delimiter = "" - noExtMediaType := media.TextType + noExtMediaType := media.Builtin.TextType var ( noExtDelimFormat = Format{ @@ -138,117 +138,10 @@ func TestGetFormatByFilename(t *testing.T) { c.Assert(found, qt.Equals, false) } -func TestDecodeFormats(t *testing.T) { - c := qt.New(t) - - mediaTypes := media.Types{media.JSONType, media.XMLType} - - tests := []struct { - name string - maps []map[string]any - shouldError bool - assert func(t *testing.T, name string, f Formats) - }{ - { - "Redefine JSON", - []map[string]any{ - { - "JsON": map[string]any{ - "baseName": "myindex", - "isPlainText": "false", - }, - }, - }, - false, - func(t *testing.T, name string, f Formats) { - msg := qt.Commentf(name) - c.Assert(len(f), qt.Equals, len(DefaultFormats), msg) - json, _ := f.GetByName("JSON") - c.Assert(json.BaseName, qt.Equals, "myindex") - c.Assert(json.MediaType, qt.Equals, media.JSONType) - c.Assert(json.IsPlainText, qt.Equals, false) - }, - }, - { - "Add XML format with string as mediatype", - []map[string]any{ - { - "MYXMLFORMAT": map[string]any{ - "baseName": "myxml", - "mediaType": "application/xml", - }, - }, - }, - false, - func(t *testing.T, name string, f Formats) { - c.Assert(len(f), qt.Equals, len(DefaultFormats)+1) - xml, found := f.GetByName("MYXMLFORMAT") - c.Assert(found, qt.Equals, true) - c.Assert(xml.BaseName, qt.Equals, "myxml") - c.Assert(xml.MediaType, qt.Equals, media.XMLType) - - // Verify that we haven't changed the DefaultFormats slice. - json, _ := f.GetByName("JSON") - c.Assert(json.BaseName, qt.Equals, "index") - }, - }, - { - "Add format unknown mediatype", - []map[string]any{ - { - "MYINVALID": map[string]any{ - "baseName": "mymy", - "mediaType": "application/hugo", - }, - }, - }, - true, - func(t *testing.T, name string, f Formats) { - }, - }, - { - "Add and redefine XML format", - []map[string]any{ - { - "MYOTHERXMLFORMAT": map[string]any{ - "baseName": "myotherxml", - "mediaType": media.XMLType, - }, - }, - { - "MYOTHERXMLFORMAT": map[string]any{ - "baseName": "myredefined", - }, - }, - }, - false, - func(t *testing.T, name string, f Formats) { - c.Assert(len(f), qt.Equals, len(DefaultFormats)+1) - xml, found := f.GetByName("MYOTHERXMLFORMAT") - c.Assert(found, qt.Equals, true) - c.Assert(xml.BaseName, qt.Equals, "myredefined") - c.Assert(xml.MediaType, qt.Equals, media.XMLType) - }, - }, - } - - for _, test := range tests { - result, err := DecodeFormats(mediaTypes, test.maps...) - msg := qt.Commentf(test.name) - - if test.shouldError { - c.Assert(err, qt.Not(qt.IsNil), msg) - } else { - c.Assert(err, qt.IsNil, msg) - test.assert(t, test.name, result) - } - } -} - func TestSort(t *testing.T) { c := qt.New(t) - c.Assert(DefaultFormats[0].Name, qt.Equals, "HTML") - c.Assert(DefaultFormats[1].Name, qt.Equals, "AMP") + c.Assert(DefaultFormats[0].Name, qt.Equals, "html") + c.Assert(DefaultFormats[1].Name, qt.Equals, "amp") json := JSONFormat json.Weight = 1 @@ -261,7 +154,7 @@ func TestSort(t *testing.T) { sort.Sort(formats) - c.Assert(formats[0].Name, qt.Equals, "JSON") - c.Assert(formats[1].Name, qt.Equals, "HTML") - c.Assert(formats[2].Name, qt.Equals, "AMP") + c.Assert(formats[0].Name, qt.Equals, "json") + c.Assert(formats[1].Name, qt.Equals, "html") + c.Assert(formats[2].Name, qt.Equals, "amp") } |