mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
hugolib, output: Add Rel to the output format
To make it super-easy to create rel-links.
This commit is contained in:
parent
29d3778ba1
commit
c7dbee2321
3 changed files with 108 additions and 11 deletions
|
@ -14,6 +14,7 @@
|
||||||
package hugolib
|
package hugolib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -116,20 +117,64 @@ type OutputFormats []*OutputFormat
|
||||||
|
|
||||||
// And OutputFormat links to a representation of a resource.
|
// And OutputFormat links to a representation of a resource.
|
||||||
type OutputFormat struct {
|
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
|
f output.Format
|
||||||
p *Page
|
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.
|
// TODO(bep) outputs consider just save this wrapper on Page.
|
||||||
// OutputFormats gives the output formats for this Page.
|
// OutputFormats gives the output formats for this Page.
|
||||||
func (p *Page) OutputFormats() OutputFormats {
|
func (p *Page) OutputFormats() OutputFormats {
|
||||||
var o OutputFormats
|
var o OutputFormats
|
||||||
|
isCanonical := len(p.outputFormats) == 1
|
||||||
for _, f := range p.outputFormats {
|
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
|
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.
|
// Get gets a OutputFormat given its name, i.e. json, html etc.
|
||||||
// It returns nil if not found.
|
// It returns nil if not found.
|
||||||
func (o OutputFormats) Get(name string) *OutputFormat {
|
func (o OutputFormats) Get(name string) *OutputFormat {
|
||||||
|
|
|
@ -15,12 +15,14 @@ package hugolib
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/hugo/helpers"
|
||||||
"github.com/spf13/hugo/output"
|
"github.com/spf13/hugo/output"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
@ -47,9 +49,19 @@ func TestDefaultOutputDefinitions(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
t.Parallel()
|
||||||
|
|
||||||
|
outputsStr := strings.Replace(fmt.Sprintf("%q", outputs), " ", ", ", -1)
|
||||||
|
|
||||||
siteConfig := `
|
siteConfig := `
|
||||||
baseURL = "http://example.com/blog"
|
baseURL = "http://example.com/blog"
|
||||||
|
|
||||||
|
@ -65,19 +77,26 @@ category = "categories"
|
||||||
|
|
||||||
pageTemplate := `---
|
pageTemplate := `---
|
||||||
title: "%s"
|
title: "%s"
|
||||||
outputs: ["html", "json"]
|
outputs: %s
|
||||||
---
|
---
|
||||||
# Doc
|
# Doc
|
||||||
`
|
`
|
||||||
|
|
||||||
th, h := newTestSitesFromConfig(t, siteConfig,
|
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)
|
require.Len(t, h.Sites, 1)
|
||||||
|
|
||||||
fs := th.Fs
|
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{})
|
err := h.Build(BuildCfg{})
|
||||||
|
|
||||||
|
@ -88,17 +107,38 @@ outputs: ["html", "json"]
|
||||||
|
|
||||||
require.NotNil(t, home)
|
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
|
// 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()
|
of := home.OutputFormats()
|
||||||
require.Len(t, of, 2)
|
require.Len(t, of, lenOut)
|
||||||
require.Nil(t, of.Get("Hugo"))
|
require.Nil(t, of.Get("Hugo"))
|
||||||
require.NotNil(t, of.Get("json"))
|
require.NotNil(t, of.Get("json"))
|
||||||
json := of.Get("JSON")
|
json := of.Get("JSON")
|
||||||
|
_, err = home.AlternativeOutputFormats()
|
||||||
|
require.Error(t, err)
|
||||||
require.NotNil(t, json)
|
require.NotNil(t, json)
|
||||||
require.Equal(t, "/blog/index.json", json.RelPermalink())
|
require.Equal(t, "/blog/index.json", json.RelPermalink())
|
||||||
require.Equal(t, "http://example.com/blog/index.json", json.Permalink())
|
require.Equal(t, "http://example.com/blog/index.json", json.Permalink())
|
||||||
|
|
|
@ -23,26 +23,26 @@ import (
|
||||||
var (
|
var (
|
||||||
// An ordered list of built-in output formats
|
// An ordered list of built-in output formats
|
||||||
// See https://www.ampproject.org/learn/overview/
|
// See https://www.ampproject.org/learn/overview/
|
||||||
// TODO
|
|
||||||
// <link rel="amphtml" href="{{ .Permalink }}">
|
|
||||||
// canonical
|
|
||||||
AMPType = Format{
|
AMPType = Format{
|
||||||
Name: "AMP",
|
Name: "AMP",
|
||||||
MediaType: media.HTMLType,
|
MediaType: media.HTMLType,
|
||||||
BaseName: "index",
|
BaseName: "index",
|
||||||
Path: "amp",
|
Path: "amp",
|
||||||
|
Rel: "amphtml",
|
||||||
}
|
}
|
||||||
|
|
||||||
CSSType = Format{
|
CSSType = Format{
|
||||||
Name: "CSS",
|
Name: "CSS",
|
||||||
MediaType: media.CSSType,
|
MediaType: media.CSSType,
|
||||||
BaseName: "styles",
|
BaseName: "styles",
|
||||||
|
Rel: "stylesheet",
|
||||||
}
|
}
|
||||||
|
|
||||||
HTMLType = Format{
|
HTMLType = Format{
|
||||||
Name: "HTML",
|
Name: "HTML",
|
||||||
MediaType: media.HTMLType,
|
MediaType: media.HTMLType,
|
||||||
BaseName: "index",
|
BaseName: "index",
|
||||||
|
Rel: "canonical",
|
||||||
}
|
}
|
||||||
|
|
||||||
JSONType = Format{
|
JSONType = Format{
|
||||||
|
@ -50,6 +50,7 @@ var (
|
||||||
MediaType: media.JSONType,
|
MediaType: media.JSONType,
|
||||||
BaseName: "index",
|
BaseName: "index",
|
||||||
IsPlainText: true,
|
IsPlainText: true,
|
||||||
|
Rel: "alternate",
|
||||||
}
|
}
|
||||||
|
|
||||||
RSSType = Format{
|
RSSType = Format{
|
||||||
|
@ -57,6 +58,7 @@ var (
|
||||||
MediaType: media.RSSType,
|
MediaType: media.RSSType,
|
||||||
BaseName: "index",
|
BaseName: "index",
|
||||||
NoUgly: true,
|
NoUgly: true,
|
||||||
|
Rel: "alternate",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -84,6 +86,16 @@ type Format struct {
|
||||||
// The base output file name used when not using "ugly URLs", defaults to "index".
|
// The base output file name used when not using "ugly URLs", defaults to "index".
|
||||||
BaseName string
|
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.
|
// The protocol to use, i.e. "webcal://". Defaults to the protocol of the baseURL.
|
||||||
Protocol string
|
Protocol string
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue