diff --git a/hugolib/hugo_sites_build.go b/hugolib/hugo_sites_build.go index 32f1d56be..970948bb3 100644 --- a/hugolib/hugo_sites_build.go +++ b/hugolib/hugo_sites_build.go @@ -177,7 +177,7 @@ func (h *HugoSites) assemble(config *BuildCfg) error { for _, p := range s.Pages { // May have been set in front matter if len(p.outputFormats) == 0 { - p.outputFormats = s.defaultOutputDefinitions.ForKind(p.Kind) + p.outputFormats = s.outputFormats[p.Kind] } if err := p.initTargetPathDescriptor(); err != nil { return err diff --git a/hugolib/page.go b/hugolib/page.go index 390df8070..4e9ea2e78 100644 --- a/hugolib/page.go +++ b/hugolib/page.go @@ -859,7 +859,7 @@ func (p *Page) RelPermalink() string { func (p *Page) initURLs() error { // TODO(bep) output if len(p.outputFormats) == 0 { - p.outputFormats = p.s.defaultOutputDefinitions.ForKind(p.Kind) + p.outputFormats = p.s.outputFormats[p.Kind] } rel := p.createRelativePermalink() p.permalink = p.s.permalink(rel) diff --git a/hugolib/page_output.go b/hugolib/page_output.go index 2d441fd44..0038b7fdd 100644 --- a/hugolib/page_output.go +++ b/hugolib/page_output.go @@ -45,7 +45,6 @@ func (p *PageOutput) targetPath(addends ...string) (string, error) { return "", err } return tp, nil - } func newPageOutput(p *Page, createCopy bool, f output.Format) (*PageOutput, error) { diff --git a/hugolib/site.go b/hugolib/site.go index 0959df3d5..1cdb285ba 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -108,7 +108,10 @@ type Site struct { disabledKinds map[string]bool - defaultOutputDefinitions siteOutputDefinitions + // Output formats defined in site config per Page Kind, or some defaults + // if not set. + // Output formats defined in Page front matter will override these. + outputFormats map[string]output.Formats // Logger etc. *deps.Deps `json:"-"` @@ -124,12 +127,12 @@ func (s *Site) isEnabled(kind string) bool { // reset returns a new Site prepared for rebuild. func (s *Site) reset() *Site { return &Site{Deps: s.Deps, - layoutHandler: output.NewLayoutHandler(s.PathSpec.ThemeSet()), - disabledKinds: s.disabledKinds, - defaultOutputDefinitions: s.defaultOutputDefinitions, - Language: s.Language, - owner: s.owner, - PageCollections: newPageCollections()} + layoutHandler: output.NewLayoutHandler(s.PathSpec.ThemeSet()), + disabledKinds: s.disabledKinds, + outputFormats: s.outputFormats, + Language: s.Language, + owner: s.owner, + PageCollections: newPageCollections()} } // newSite creates a new site with the given configuration. @@ -145,14 +148,18 @@ func newSite(cfg deps.DepsCfg) (*Site, error) { disabledKinds[disabled] = true } - outputDefs := createSiteOutputDefinitions(cfg.Cfg) + outputFormats, err := createSiteOutputFormats(cfg.Language) + + if err != nil { + return nil, err + } s := &Site{ - PageCollections: c, - layoutHandler: output.NewLayoutHandler(cfg.Cfg.GetString("themesDir") != ""), - Language: cfg.Language, - disabledKinds: disabledKinds, - defaultOutputDefinitions: outputDefs, + PageCollections: c, + layoutHandler: output.NewLayoutHandler(cfg.Cfg.GetString("themesDir") != ""), + Language: cfg.Language, + disabledKinds: disabledKinds, + outputFormats: outputFormats, } s.Info = newSiteInfo(siteBuilderCfg{s: s, pageCollections: c, language: s.Language}) @@ -2007,7 +2014,7 @@ func (s *Site) newNodePage(typ string, sections ...string) *Page { sections: sections, s: s} - p.outputFormats = p.s.defaultOutputDefinitions.ForKind(typ) + p.outputFormats = p.s.outputFormats[p.Kind] return p diff --git a/hugolib/site_output.go b/hugolib/site_output.go index 6b27bb1a1..8b0dd7d63 100644 --- a/hugolib/site_output.go +++ b/hugolib/site_output.go @@ -14,56 +14,81 @@ package hugolib import ( + "fmt" "path" "strings" + "github.com/spf13/cast" "github.com/spf13/hugo/config" "github.com/spf13/hugo/output" ) -type siteOutputDefinitions []siteOutputDefinition +func createSiteOutputFormats(cfg config.Provider) (map[string]output.Formats, error) { + if !cfg.IsSet("outputs") { + return createDefaultOutputFormats(cfg) + } -type siteOutputDefinition struct { - // What Kinds of pages are excluded in this definition. - // A blank strings means NONE. - // Comma separated list (for now). - ExcludedKinds string + outFormats := make(map[string]output.Formats) - Outputs []output.Format -} + outputs := cfg.GetStringMap("outputs") -func (defs siteOutputDefinitions) ForKind(kind string) []output.Format { - var result []output.Format + if outputs == nil || len(outputs) == 0 { + // TODO(bep) outputs log a warning? + return outFormats, nil + } - for _, def := range defs { - if def.ExcludedKinds == "" || !strings.Contains(def.ExcludedKinds, kind) { - result = append(result, def.Outputs...) + for k, v := range outputs { + var formats output.Formats + vals := cast.ToStringSlice(v) + for _, format := range vals { + f, found := output.GetFormat(format) + if !found { + return nil, fmt.Errorf("Failed to resolve output format %q from site config", format) + } + formats = append(formats, f) + } + + if len(formats) > 0 { + outFormats[k] = formats } } - return result -} - -func createSiteOutputDefinitions(cfg config.Provider) siteOutputDefinitions { - - var defs siteOutputDefinitions - - // All have HTML - defs = append(defs, siteOutputDefinition{ExcludedKinds: "", Outputs: []output.Format{output.HTMLType}}) - - // TODO(bep) output deprecate rssURI - rssBase := cfg.GetString("rssURI") - if rssBase == "" { - rssBase = "index" + // Make sure every kind has at least one output format + for _, kind := range allKinds { + if _, found := outFormats[kind]; !found { + outFormats[kind] = output.Formats{output.HTMLType} + } } - // RSS has now a well defined media type, so strip any suffix provided - rssBase = strings.TrimSuffix(rssBase, path.Ext(rssBase)) - rssType := output.RSSType - rssType.BaseName = rssBase + return outFormats, nil - // Some have RSS - defs = append(defs, siteOutputDefinition{ExcludedKinds: "page", Outputs: []output.Format{rssType}}) - - return defs +} + +func createDefaultOutputFormats(cfg config.Provider) (map[string]output.Formats, error) { + outFormats := make(map[string]output.Formats) + for _, kind := range allKinds { + var formats output.Formats + // All have HTML + formats = append(formats, output.HTMLType) + + // All but page have RSS + if kind != KindPage { + // TODO(bep) output deprecate rssURI + rssBase := cfg.GetString("rssURI") + if rssBase == "" { + rssBase = "index" + } + + // RSS has now a well defined media type, so strip any suffix provided + rssBase = strings.TrimSuffix(rssBase, path.Ext(rssBase)) + rssType := output.RSSType + rssType.BaseName = rssBase + formats = append(formats, rssType) + + } + + outFormats[kind] = formats + } + + return outFormats, nil } diff --git a/hugolib/site_output_test.go b/hugolib/site_output_test.go index 85b3291fe..6694a9bfd 100644 --- a/hugolib/site_output_test.go +++ b/hugolib/site_output_test.go @@ -27,23 +27,25 @@ import ( "github.com/spf13/viper" ) -func TestDefaultOutputDefinitions(t *testing.T) { +func TestDefaultOutputFormats(t *testing.T) { t.Parallel() - defs := createSiteOutputDefinitions(viper.New()) + defs, err := createDefaultOutputFormats(viper.New()) + + require.NoError(t, err) tests := []struct { name string kind string - want []output.Format + want output.Formats }{ - {"RSS not for regular pages", KindPage, []output.Format{output.HTMLType}}, - {"Home Sweet Home", KindHome, []output.Format{output.HTMLType, output.RSSType}}, + {"RSS not for regular pages", KindPage, output.Formats{output.HTMLType}}, + {"Home Sweet Home", KindHome, output.Formats{output.HTMLType, output.RSSType}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := defs.ForKind(tt.kind); !reflect.DeepEqual(got, tt.want) { - t.Errorf("siteOutputDefinitions.ForKind(%v) = %v, want %v", tt.kind, got, tt.want) + if got := defs[tt.kind]; !reflect.DeepEqual(got, tt.want) { + t.Errorf("createDefaultOutputFormats(%v) = %v, want %v", tt.kind, got, tt.want) } }) } @@ -57,6 +59,7 @@ func TestSiteWithPageOutputs(t *testing.T) { } } +// TODO(bep) output add test for site outputs config func doTestSiteWithPageOutputs(t *testing.T, outputs []string) { t.Parallel() diff --git a/media/mediaType_test.go b/media/mediaType_test.go index 41cc48369..e9de66d0e 100644 --- a/media/mediaType_test.go +++ b/media/mediaType_test.go @@ -24,14 +24,14 @@ func TestDefaultTypes(t *testing.T) { require.Equal(t, "html", HTMLType.SubType) require.Equal(t, "html", HTMLType.Suffix) - require.Equal(t, "text/html", HTMLType.MainType()) + require.Equal(t, "text/html", HTMLType.Type()) require.Equal(t, "text/html+html", HTMLType.String()) require.Equal(t, "application", RSSType.MainType) require.Equal(t, "rss", RSSType.SubType) require.Equal(t, "xml", RSSType.Suffix) - require.Equal(t, "application/rss", RSSType.MainType()) + require.Equal(t, "application/rss", RSSType.Type()) require.Equal(t, "application/rss+xml", RSSType.String()) } diff --git a/output/layout_base.go b/output/layout_base.go index 929ee07a2..6b26a4c5b 100644 --- a/output/layout_base.go +++ b/output/layout_base.go @@ -56,11 +56,11 @@ type TemplateLookupDescriptor struct { ContainsAny func(filename string, subslices [][]byte) (bool, error) } -func CreateTemplateID(d TemplateLookupDescriptor) (TemplateNames, error) { +func CreateTemplateNames(d TemplateLookupDescriptor) (TemplateNames, error) { var id TemplateNames - name := filepath.FromSlash(d.RelPath) + name := filepath.ToSlash(d.RelPath) if d.Prefix != "" { name = strings.Trim(d.Prefix, "/") + "/" + name diff --git a/output/layout_base_test.go b/output/layout_base_test.go index 60d9b8c62..f20d99bef 100644 --- a/output/layout_base_test.go +++ b/output/layout_base_test.go @@ -124,6 +124,8 @@ func TestLayoutBase(t *testing.T) { } { t.Run(this.name, func(t *testing.T) { + this.basePathMatchStrings = filepath.FromSlash(this.basePathMatchStrings) + fileExists := func(filename string) (bool, error) { stringsToMatch := strings.Split(this.basePathMatchStrings, "|") for _, s := range stringsToMatch { @@ -148,7 +150,7 @@ func TestLayoutBase(t *testing.T) { this.expect.MasterFilename = filepath.FromSlash(this.expect.MasterFilename) this.expect.OverlayFilename = filepath.FromSlash(this.expect.OverlayFilename) - id, err := CreateTemplateID(this.d) + id, err := CreateTemplateNames(this.d) require.NoError(t, err) require.Equal(t, this.expect, id, this.name) diff --git a/tpl/tplimpl/template.go b/tpl/tplimpl/template.go index bf4587e8e..04f932ddd 100644 --- a/tpl/tplimpl/template.go +++ b/tpl/tplimpl/template.go @@ -508,7 +508,7 @@ func (t *GoHTMLTemplate) loadTemplates(absPath string, prefix string) { }, } - tplID, err := output.CreateTemplateID(descriptor) + tplID, err := output.CreateTemplateNames(descriptor) if err != nil { t.Log.ERROR.Printf("Failed to resolve template in path %q: %s", path, err) return nil