summaryrefslogtreecommitdiffstats
path: root/output
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2017-03-27 20:43:49 +0200
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2017-04-02 23:13:10 +0200
commit8b5b558bb515e80da640f5e114169874771b61e4 (patch)
tree5755e77efe3120963d012dccbd91d916180485e0 /output
parent27610ddd011e8172d00e02275f948c3f1ed43e4f (diff)
tpl: Rework to handle both text and HTML templates
Before this commit, Hugo used `html/template` for all Go templates. While this is a fine choice for HTML and maybe also RSS feeds, it is painful for plain text formats such as CSV, JSON etc. This commit fixes that by using the `IsPlainText` attribute on the output format to decide what to use. A couple of notes: * The above requires a nonambiguous template name to type mapping. I.e. `/layouts/_default/list.json` will only work if there is only one JSON output format, `/layouts/_default/list.mytype.json` will always work. * Ambiguous types will fall back to HTML. * Partials inherits the text vs HTML identificator of the container template. This also means that plain text templates can only include plain text partials. * Shortcode templates are, by definition, currently HTML templates only. Fixes #3221
Diffstat (limited to 'output')
-rw-r--r--output/layout.go28
-rw-r--r--output/layout_base.go19
-rw-r--r--output/layout_base_test.go6
-rw-r--r--output/layout_test.go4
-rw-r--r--output/outputFormat.go40
-rw-r--r--output/outputFormat_test.go27
6 files changed, 118 insertions, 6 deletions
diff --git a/output/layout.go b/output/layout.go
index a2bfd7717..6dba7f3b4 100644
--- a/output/layout.go
+++ b/output/layout.go
@@ -152,9 +152,11 @@ func (l *LayoutHandler) For(d LayoutDescriptor, layoutOverride string, f Format)
}
}
- return layoutsWithThemeLayouts, nil
+ layouts = layoutsWithThemeLayouts
}
+ layouts = prependTextPrefixIfNeeded(f, layouts...)
+
l.mu.Lock()
l.cache[key] = layouts
l.mu.Unlock()
@@ -184,10 +186,26 @@ func resolveListTemplate(d LayoutDescriptor, f Format,
}
func resolveTemplate(templ string, d LayoutDescriptor, f Format) []string {
- return strings.Fields(replaceKeyValues(templ,
+ layouts := strings.Fields(replaceKeyValues(templ,
"SUFFIX", f.MediaType.Suffix,
"NAME", strings.ToLower(f.Name),
"SECTION", d.Section))
+
+ return layouts
+}
+
+func prependTextPrefixIfNeeded(f Format, layouts ...string) []string {
+ if !f.IsPlainText {
+ return layouts
+ }
+
+ newLayouts := make([]string, len(layouts))
+
+ for i, l := range layouts {
+ newLayouts[i] = "_text/" + l
+ }
+
+ return newLayouts
}
func replaceKeyValues(s string, oldNew ...string) string {
@@ -195,7 +213,9 @@ func replaceKeyValues(s string, oldNew ...string) string {
return replacer.Replace(s)
}
-func regularPageLayouts(types string, layout string, f Format) (layouts []string) {
+func regularPageLayouts(types string, layout string, f Format) []string {
+ var layouts []string
+
if layout == "" {
layout = "single"
}
@@ -219,5 +239,5 @@ func regularPageLayouts(types string, layout string, f Format) (layouts []string
layouts = append(layouts, fmt.Sprintf("_default/%s.%s.%s", layout, name, suffix))
layouts = append(layouts, fmt.Sprintf("_default/%s.%s", layout, suffix))
- return
+ return layouts
}
diff --git a/output/layout_base.go b/output/layout_base.go
index 2bb89c20d..a0d2bc4eb 100644
--- a/output/layout_base.go
+++ b/output/layout_base.go
@@ -29,7 +29,10 @@ var (
)
type TemplateNames struct {
- Name string
+ // The name used as key in the template map. Note that this will be
+ // prefixed with "_text/" if it should be parsed with text/template.
+ Name string
+
OverlayFilename string
MasterFilename string
}
@@ -51,6 +54,10 @@ type TemplateLookupDescriptor struct {
// The theme name if active.
Theme string
+ // All the output formats in play. This is used to decide if text/template or
+ // html/template.
+ OutputFormats Formats
+
FileExists func(filename string) (bool, error)
ContainsAny func(filename string, subslices [][]byte) (bool, error)
}
@@ -74,6 +81,12 @@ func CreateTemplateNames(d TemplateLookupDescriptor) (TemplateNames, error) {
// index.amp.html
// index.json
filename := filepath.Base(d.RelPath)
+ isPlainText := false
+ outputFormat, found := d.OutputFormats.FromFilename(filename)
+
+ if found && outputFormat.IsPlainText {
+ isPlainText = true
+ }
var ext, outFormat string
@@ -90,6 +103,10 @@ func CreateTemplateNames(d TemplateLookupDescriptor) (TemplateNames, error) {
id.OverlayFilename = fullPath
id.Name = name
+ if isPlainText {
+ id.Name = "_text/" + id.Name
+ }
+
// Ace and Go templates may have both a base and inner template.
pathDir := filepath.Dir(fullPath)
diff --git a/output/layout_base_test.go b/output/layout_base_test.go
index f20d99bef..16be615f2 100644
--- a/output/layout_base_test.go
+++ b/output/layout_base_test.go
@@ -141,6 +141,7 @@ func TestLayoutBase(t *testing.T) {
return this.needsBase, nil
}
+ this.d.OutputFormats = Formats{AMPFormat, HTMLFormat, RSSFormat, JSONFormat}
this.d.WorkingDir = filepath.FromSlash(this.d.WorkingDir)
this.d.LayoutDir = filepath.FromSlash(this.d.LayoutDir)
this.d.RelPath = filepath.FromSlash(this.d.RelPath)
@@ -150,6 +151,11 @@ func TestLayoutBase(t *testing.T) {
this.expect.MasterFilename = filepath.FromSlash(this.expect.MasterFilename)
this.expect.OverlayFilename = filepath.FromSlash(this.expect.OverlayFilename)
+ if strings.Contains(this.d.RelPath, "json") {
+ // currently the only plain text templates in this test.
+ this.expect.Name = "_text/" + this.expect.Name
+ }
+
id, err := CreateTemplateNames(this.d)
require.NoError(t, err)
diff --git a/output/layout_test.go b/output/layout_test.go
index e59a16fcb..6ea5a7617 100644
--- a/output/layout_test.go
+++ b/output/layout_test.go
@@ -64,6 +64,10 @@ func TestLayout(t *testing.T) {
[]string{"taxonomy/tag.rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"}},
{"RSS Taxonomy term", LayoutDescriptor{Kind: "taxonomyTerm", Section: "tag"}, false, "", RSSFormat,
[]string{"taxonomy/tag.terms.rss.xml", "_default/rss.xml", "rss.xml", "_internal/_default/rss.xml"}},
+ {"Home plain text", LayoutDescriptor{Kind: "home"}, true, "", JSONFormat,
+ []string{"_text/index.json.json", "_text/index.json", "_text/_default/list.json.json", "_text/_default/list.json", "_text/theme/index.json.json", "_text/theme/index.json"}},
+ {"Page plain text", LayoutDescriptor{Kind: "page"}, true, "", JSONFormat,
+ []string{"_text/_default/single.json.json", "_text/_default/single.json", "_text/theme/_default/single.json.json"}},
} {
t.Run(this.name, func(t *testing.T) {
l := NewLayoutHandler(this.hasTheme)
diff --git a/output/outputFormat.go b/output/outputFormat.go
index 76329a936..9d43b135a 100644
--- a/output/outputFormat.go
+++ b/output/outputFormat.go
@@ -33,6 +33,7 @@ var (
IsHTML: true,
}
+ // CalendarFormat is AAA
CalendarFormat = Format{
Name: "Calendar",
MediaType: media.CalendarType,
@@ -104,6 +105,45 @@ func (formats Formats) GetByName(name string) (f Format, found bool) {
return
}
+func (formats Formats) GetBySuffix(name string) (f Format, found bool) {
+ for _, ff := range formats {
+ if name == ff.MediaType.Suffix {
+ if found {
+ // ambiguous
+ found = false
+ return
+ }
+ f = ff
+ found = true
+ }
+ }
+ return
+}
+
+func (formats Formats) FromFilename(filename string) (f Format, found bool) {
+ // mytemplate.amp.html
+ // mytemplate.html
+ // mytemplate
+ var ext, outFormat string
+
+ parts := strings.Split(filename, ".")
+ if len(parts) > 2 {
+ outFormat = parts[1]
+ ext = parts[2]
+ } else if len(parts) > 1 {
+ ext = parts[1]
+ }
+
+ if outFormat != "" {
+ return formats.GetByName(outFormat)
+ }
+
+ if ext != "" {
+ return formats.GetBySuffix(ext)
+ }
+ return
+}
+
// Format represents an output representation, usually to a file on disk.
type Format struct {
// The Name is used as an identifier. Internal output formats (i.e. HTML and RSS)
diff --git a/output/outputFormat_test.go b/output/outputFormat_test.go
index e742012ba..b73e53f82 100644
--- a/output/outputFormat_test.go
+++ b/output/outputFormat_test.go
@@ -65,7 +65,7 @@ func TestDefaultTypes(t *testing.T) {
}
-func TestGetType(t *testing.T) {
+func TestGetFormat(t *testing.T) {
tp, _ := GetFormat("html")
require.Equal(t, HTMLFormat, tp)
tp, _ = GetFormat("HTML")
@@ -73,3 +73,28 @@ func TestGetType(t *testing.T) {
_, found := GetFormat("FOO")
require.False(t, found)
}
+
+func TestGeGetFormatByName(t *testing.T) {
+ formats := Formats{AMPFormat, CalendarFormat}
+ tp, _ := formats.GetByName("AMP")
+ require.Equal(t, AMPFormat, tp)
+ _, found := formats.GetByName("HTML")
+ require.False(t, found)
+ _, found = formats.GetByName("FOO")
+ require.False(t, found)
+}
+
+func TestGeGetFormatByExt(t *testing.T) {
+ formats1 := Formats{AMPFormat, CalendarFormat}
+ formats2 := Formats{AMPFormat, HTMLFormat, CalendarFormat}
+ tp, _ := formats1.GetBySuffix("html")
+ require.Equal(t, AMPFormat, tp)
+ tp, _ = formats1.GetBySuffix("ics")
+ require.Equal(t, CalendarFormat, tp)
+ _, found := formats1.GetBySuffix("not")
+ require.False(t, found)
+
+ // ambiguous
+ _, found = formats2.GetByName("html")
+ require.False(t, found)
+}