internal/web: allow markdown files to turn off templates

If a markdown file has `template: false` in its metadata,
it will not be treated as a template.

Change-Id: I81599ea02b05ff7693f5f816293433df4988b64c
Reviewed-on: https://go-review.googlesource.com/c/website/+/537497
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/internal/web/render.go b/internal/web/render.go
index 16c3822..bb3ef1b 100644
--- a/internal/web/render.go
+++ b/internal/web/render.go
@@ -124,24 +124,32 @@
 
 	var buf bytes.Buffer
 	if _, ok := p["Content"]; !ok && data != "" {
-		// Load actual Markdown content (also a template).
-		tf := t.New(file)
-		if err := tmplfunc.Parse(tf, data); err != nil {
-			return nil, err
+		// Either the page explicitly requested templating, or it is markdown,
+		// which is treated as a template by default.
+		isTemplate, explicit := p["template"].(bool)
+		tdata := data
+		if !explicit || isTemplate {
+			// Load content as a template.
+			tf := t.New(file)
+			if err := tmplfunc.Parse(tf, data); err != nil {
+				return nil, err
+			}
+			if err := tf.Execute(&buf, p); err != nil {
+				return nil, err
+			}
+			tdata = buf.String()
+			buf.Reset()
 		}
-		if err := tf.Execute(&buf, p); err != nil {
-			return nil, err
-		}
+
 		if strings.HasSuffix(file, ".md") {
-			html, err := markdownToHTML(buf.String())
+			html, err := markdownToHTML(tdata)
 			if err != nil {
 				return nil, err
 			}
 			p["Content"] = html
 		} else {
-			p["Content"] = template.HTML(buf.String())
+			p["Content"] = template.HTML(tdata)
 		}
-		buf.Reset()
 	}
 
 	if err := t.Execute(&buf, p); err != nil {
diff --git a/internal/web/site.go b/internal/web/site.go
index 1b1a72f..81e8dee 100644
--- a/internal/web/site.go
+++ b/internal/web/site.go
@@ -51,6 +51,10 @@
 // The key-value pair “layout: name” selects the page layout template with the given name.
 // See the next section, “Page Rendering”, for details about layout and rendering.
 //
+// The key-value pair “template: bool” controls whether the page is treated as an HTML template
+// (see the next section, “Page Rendering”). The default is false for HTML
+// and true for markdown.
+//
 // In addition to these explicit key-value pairs, pages loaded from the file system
 // have a few implicit key-value pairs added by the page loading process:
 //
@@ -529,8 +533,8 @@
 		src = buf.String()
 	}
 
-	// Template is enabled always in Markdown.
-	// It can only be disabled for HTML files.
+	// If the file doesn't ask to be treated as a template and isn't Markdown,
+	// set the page's content to skip templating later, in Site.renderHTML.
 	isTemplate, _ := p.page["template"].(bool)
 	if !isTemplate && !isMarkdown {
 		p.page["Content"] = template.HTML(src)
diff --git a/internal/web/site_test.go b/internal/web/site_test.go
index 793038f..3fb0d83 100644
--- a/internal/web/site_test.go
+++ b/internal/web/site_test.go
@@ -53,14 +53,15 @@
 
 func TestMarkdown(t *testing.T) {
 	site := NewSite(fstest.MapFS{
-		"site.tmpl":           {Data: []byte(`{{.Content}}`)},
-		"doc/test.md":         {Data: []byte("**bold**")},
-		"doc/test2.md":        {Data: []byte(`{{"*template*"}}`)},
-		"lib/godoc/site.html": {Data: []byte(`{{.Data}}`)},
+		"site.tmpl":    {Data: []byte(`{{.Content}}`)},
+		"doc/test.md":  {Data: []byte("**bold**")},
+		"doc/test2.md": {Data: []byte(`{{"*template*"}}`)},
+		"doc/test3.md": {Data: []byte("---\ntemplate: false\n---\n{{x}}")},
 	})
 
 	testServeBody(t, site, "/doc/test", "<strong>bold</strong>")
 	testServeBody(t, site, "/doc/test2", "<em>template</em>")
+	testServeBody(t, site, "/doc/test3", `{{x}}`)
 }
 
 func TestCode(t *testing.T) {