shithub: hugo

Download patch

ref: c7dbee2321af2f0d61bdc976829681f3799582a9
parent: 29d3778ba10f806cc2e252c4eec0f3798905c9a6
author: Bjørn Erik Pedersen <[email protected]>
date: Wed Mar 22 05:54:56 EDT 2017

hugolib, output: Add Rel to the output format

To make it super-easy to create rel-links.

--- a/hugolib/page_output.go
+++ b/hugolib/page_output.go
@@ -14,6 +14,7 @@
 package hugolib
 
 import (
+	"fmt"
 	"html/template"
 	"strings"
 	"sync"
@@ -116,18 +117,62 @@
 
 // And OutputFormat links to a representation of a resource.
 type OutputFormat struct {
+	// Rel constains a value that can be used to construct a rel link.
+	// This is value is fetched from the output format definition.
+	// Note that for pages with only one output format,
+	// this method will always return "canonical".
+	// TODO(bep) output -- the above may not be correct for CSS etc. Figure out a way around that.
+	// TODO(bep) output -- re the above, maybe add a "alternate" filter to AlternativeOutputFormats.
+	// As an example, the AMP output format will, by default, return "amphtml".
+	//
+	// See:
+	// https://www.ampproject.org/docs/guides/deploy/discovery
+	//
+	// Most other output formats will have "alternate" as value for this.
+	Rel string
+
+	// It may be tempting to export this, but let us hold on to that horse for a while.
 	f output.Format
 	p *Page
 }
 
+// Name returns this OutputFormat's name, i.e. HTML, AMP, JSON etc.
+func (o OutputFormat) Name() string {
+	return o.f.Name
+}
+
 // TODO(bep) outputs consider just save this wrapper on Page.
 // OutputFormats gives the output formats for this Page.
 func (p *Page) OutputFormats() OutputFormats {
 	var o OutputFormats
+	isCanonical := len(p.outputFormats) == 1
 	for _, f := range p.outputFormats {
-		o = append(o, &OutputFormat{f: f, p: p})
+		rel := f.Rel
+		if isCanonical {
+			rel = "canonical"
+		}
+		o = append(o, &OutputFormat{Rel: rel, f: f, p: p})
 	}
 	return o
+}
+
+// OutputFormats gives the alternative output formats for this PageOutput.
+func (p *PageOutput) AlternativeOutputFormats() (OutputFormats, error) {
+	var o OutputFormats
+	for _, of := range p.OutputFormats() {
+		if of.f == p.outputFormat {
+			continue
+		}
+		o = append(o, of)
+	}
+	return o, nil
+}
+
+// AlternativeOutputFormats is only available on the top level rendering
+// entry point, and not inside range loops on the Page collections.
+// This method is just here to inform users of that restriction.
+func (p *Page) AlternativeOutputFormats() (OutputFormats, error) {
+	return nil, fmt.Errorf("AlternativeOutputFormats only available from the top level template context for page %q", p.Path())
 }
 
 // Get gets a OutputFormat given its name, i.e. json, html etc.
--- a/hugolib/site_output_test.go
+++ b/hugolib/site_output_test.go
@@ -15,6 +15,7 @@
 
 import (
 	"reflect"
+	"strings"
 	"testing"
 
 	"github.com/stretchr/testify/require"
@@ -21,6 +22,7 @@
 
 	"fmt"
 
+	"github.com/spf13/hugo/helpers"
 	"github.com/spf13/hugo/output"
 	"github.com/spf13/viper"
 )
@@ -47,9 +49,19 @@
 	}
 }
 
-func TestSiteWithJSONHomepage(t *testing.T) {
+func TestSiteWithPageOutputs(t *testing.T) {
+	for _, outputs := range [][]string{{"html", "json"}, {"json"}} {
+		t.Run(fmt.Sprintf("%v", outputs), func(t *testing.T) {
+			doTestSiteWithPageOutputs(t, outputs)
+		})
+	}
+}
+
+func doTestSiteWithPageOutputs(t *testing.T, outputs []string) {
 	t.Parallel()
 
+	outputsStr := strings.Replace(fmt.Sprintf("%q", outputs), " ", ", ", -1)
+
 	siteConfig := `
 baseURL = "http://example.com/blog"
 
@@ -65,19 +77,26 @@
 
 	pageTemplate := `---
 title: "%s"
-outputs: ["html", "json"]
+outputs: %s
 ---
 # Doc
 `
 
 	th, h := newTestSitesFromConfig(t, siteConfig,
-		"layouts/_default/list.json", "List JSON|{{ .Title }}|{{ .Content }}",
+		"layouts/_default/list.json", `List JSON|{{ .Title }}|{{ .Content }}|Alt formats: {{ len .AlternativeOutputFormats -}}|
+{{- range .AlternativeOutputFormats -}}
+Alt Output: {{ .Name -}}|
+{{- end -}}|
+{{- range .OutputFormats -}}
+Output/Rel: {{ .Name -}}/{{ .Rel }}|
+{{- end -}}
+`,
 	)
 	require.Len(t, h.Sites, 1)
 
 	fs := th.Fs
 
-	writeSource(t, fs, "content/_index.md", fmt.Sprintf(pageTemplate, "JSON Home"))
+	writeSource(t, fs, "content/_index.md", fmt.Sprintf(pageTemplate, "JSON Home", outputsStr))
 
 	err := h.Build(BuildCfg{})
 
@@ -88,17 +107,38 @@
 
 	require.NotNil(t, home)
 
-	require.Len(t, home.outputFormats, 2)
+	lenOut := len(outputs)
 
+	require.Len(t, home.outputFormats, lenOut)
+
 	// TODO(bep) output assert template/text
+	// There is currently always a JSON output to make it simpler ...
+	altFormats := lenOut - 1
+	hasHTML := helpers.InStringArray(outputs, "html")
+	th.assertFileContent("public/index.json",
+		"List JSON",
+		fmt.Sprintf("Alt formats: %d", altFormats),
+	)
 
-	th.assertFileContent("public/index.json", "List JSON")
+	if hasHTML {
+		th.assertFileContent("public/index.json",
+			"Alt Output: HTML",
+			"Output/Rel: JSON/alternate|",
+			"Output/Rel: HTML/canonical|",
+		)
+	} else {
+		th.assertFileContent("public/index.json",
+			"Output/Rel: JSON/canonical|",
+		)
+	}
 
 	of := home.OutputFormats()
-	require.Len(t, of, 2)
+	require.Len(t, of, lenOut)
 	require.Nil(t, of.Get("Hugo"))
 	require.NotNil(t, of.Get("json"))
 	json := of.Get("JSON")
+	_, err = home.AlternativeOutputFormats()
+	require.Error(t, err)
 	require.NotNil(t, json)
 	require.Equal(t, "/blog/index.json", json.RelPermalink())
 	require.Equal(t, "http://example.com/blog/index.json", json.Permalink())
--- a/output/outputFormat.go
+++ b/output/outputFormat.go
@@ -23,14 +23,12 @@
 var (
 	// An ordered list of built-in output formats
 	// See https://www.ampproject.org/learn/overview/
-	// TODO
-	// <link rel="amphtml" href="{{ .Permalink }}">
-	// canonical
 	AMPType = Format{
 		Name:      "AMP",
 		MediaType: media.HTMLType,
 		BaseName:  "index",
 		Path:      "amp",
+		Rel:       "amphtml",
 	}
 
 	CSSType = Format{
@@ -37,6 +35,7 @@
 		Name:      "CSS",
 		MediaType: media.CSSType,
 		BaseName:  "styles",
+		Rel:       "stylesheet",
 	}
 
 	HTMLType = Format{
@@ -43,6 +42,7 @@
 		Name:      "HTML",
 		MediaType: media.HTMLType,
 		BaseName:  "index",
+		Rel:       "canonical",
 	}
 
 	JSONType = Format{
@@ -50,6 +50,7 @@
 		MediaType:   media.JSONType,
 		BaseName:    "index",
 		IsPlainText: true,
+		Rel:         "alternate",
 	}
 
 	RSSType = Format{
@@ -57,6 +58,7 @@
 		MediaType: media.RSSType,
 		BaseName:  "index",
 		NoUgly:    true,
+		Rel:       "alternate",
 	}
 )
 
@@ -83,6 +85,16 @@
 
 	// The base output file name used when not using "ugly URLs", defaults to "index".
 	BaseName string
+
+	// 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">
+	Rel string
 
 	// The protocol to use, i.e. "webcal://". Defaults to the protocol of the baseURL.
 	Protocol string