Repository: caddyserver/caddy (language: go)

Issue:
Template function to include file without parsing that file as a template (ie. includeRaw)
I'm trying to create a simple markdown-based blog using Caddy. One issue I'm running in to though, is when I try to include the markdown file to `splitFrontMatter` on it (or later to render it), I run into an issue where the contents of the markdown file are parsed as a template. 

A possible workaround for this is to set `between` on the `templates` directive [as linked here](https://caddyserver.com/docs/caddyfile/directives/templates), but, again, this is more of a workaround than a real fix. Specifically, this fix wouldn't work in the case where you're trying to document how you created the blog, in a blog post, since you'd need to show the templates in that post!

What I'm proposing is a `includeRaw` template function, where the contents of the file are read and returned, but without parsing the given file as a template, to prevent issues like the infinite recursion I was hitting when trying to use `include` in my use case.


Discussion / hints:
So my understanding is you want to escape literal template delimiters?

You can do something like this:

```
{{"{{"}}
{{"}}"}}
```

Funky, I know, but it works. :man_shrugging: 
The idea was that I could avoid having to do weird escaping on the source markdown file, such that it would render the same as it would in any other markdown renderer.
Gotcha. Okay, yeah, an `includeRaw` might be a good way to go.

Fix patch:
diff --git a/modules/caddyhttp/templates/templates.go b/modules/caddyhttp/templates/templates.go
index 449536ac1a7..65359d9e9aa 100644
--- a/modules/caddyhttp/templates/templates.go
+++ b/modules/caddyhttp/templates/templates.go
@@ -88,7 +88,11 @@ func init() {
 //
 // ##### `httpInclude`
 //
-// Includes the contents of another file by making a virtual HTTP request (also known as a sub-request). The URI path must exist on the same virtual server because the request does not use sockets; instead, the request is crafted in memory and the handler is invoked directly for increased efficiency.
+// Includes the contents of another file, and renders it in-place,
+// by making a virtual HTTP request (also known as a sub-request).
+// The URI path must exist on the same virtual server because the
+// request does not use sockets; instead, the request is crafted in
+// memory and the handler is invoked directly for increased efficiency.
 //
 // ```
 // {{httpInclude "/foo/bar?q=val"}}
@@ -96,7 +100,13 @@ func init() {
 //
 // ##### `import`
 //
-// Imports the contents of another file and adds any template definitions to the template stack. If there are no defitions, the filepath will be the defition name. Any {{ define }} blocks will be accessible by {{ template }} or {{ block }}. Imports must happen before the template or block action is called
+// Reads and returns the contents of another file, and parses it
+// as a template, adding any template definitions to the template
+// stack. If there are no definitions, the filepath will be the
+// definition name. Any {{ define }} blocks will be accessible by
+// {{ template }} or {{ block }}. Imports must happen before the
+// template or block action is called. Note that the contents are
+// NOT escaped, so you should only import trusted template files.
 //
 // **filename.html**
 // ```
@@ -113,13 +123,26 @@ func init() {
 //
 // ##### `include`
 //
-// Includes the contents of another file and renders in-place. Optionally can pass key-value pairs as arguments to be accessed by the included file.
+// Includes the contents of another file, rendering it in-place.
+// Optionally can pass key-value pairs as arguments to be accessed
+// by the included file. Note that the contents are NOT escaped,
+// so you should only include trusted template files.
 //
 // ```
 // {{include "path/to/file.html"}}  // no arguments
 // {{include "path/to/file.html" "arg1" 2 "value 3"}}  // with arguments
 // ```
 //
+// ##### `readFile`
+//
+// Reads and returns the contents of another file, as-is.
+// Note that the contents are NOT escaped, so you should
+// only read trusted files.
+//
+// ```
+// {{readFile "path/to/file.html"}}
+// ```
+//
 // ##### `listFiles`
 //
 // Returns a list of the files in the given directory, which is relative to the template context's file root.
@@ -130,10 +153,10 @@ func init() {
 //
 // ##### `markdown`
 //
-// Renders the given Markdown text as HTML. This uses the
+// Renders the given Markdown text as HTML and returns it. This uses the
 // [Goldmark](https://github.com/yuin/goldmark) library,
-// which is CommonMark compliant. It also has these plugins
-// enabled: Github Flavored Markdown, Footnote and syntax
+// which is CommonMark compliant. It also has these extensions
+// enabled: Github Flavored Markdown, Footnote, and syntax
 // highlighting provided by [Chroma](https://github.com/alecthomas/chroma).
 //
 // ```
diff --git a/modules/caddyhttp/templates/tplcontext.go b/modules/caddyhttp/templates/tplcontext.go
index fb00f4fe0ec..5dc4ae55326 100644
--- a/modules/caddyhttp/templates/tplcontext.go
+++ b/modules/caddyhttp/templates/tplcontext.go
@@ -30,7 +30,7 @@ import (
 	"time"
 
 	"github.com/Masterminds/sprig/v3"
-	"github.com/alecthomas/chroma/v2/formatters/html"
+	chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
 	"github.com/caddyserver/caddy/v2"
 	"github.com/caddyserver/caddy/v2/modules/caddyhttp"
 	"github.com/dustin/go-humanize"
@@ -74,6 +74,7 @@ func (c *TemplateContext) NewTemplate(tplName string) *template.Template {
 	// add our own library
 	c.tpl.Funcs(template.FuncMap{
 		"include":          c.funcInclude,
+		"readFile":         c.funcReadFile,
 		"import":           c.funcImport,
 		"httpInclude":      c.funcHTTPInclude,
 		"stripHTML":        c.funcStripHTML,
@@ -123,6 +124,23 @@ func (c TemplateContext) funcInclude(filename string, args ...any) (string, erro
 	return bodyBuf.String(), nil
 }
 
+// funcReadFile returns the contents of a filename relative to the site root.
+// Note that included files are NOT escaped, so you should only include
+// trusted files. If it is not trusted, be sure to use escaping functions
+// in your template.
+func (c TemplateContext) funcReadFile(filename string) (string, error) {
+	bodyBuf := bufPool.Get().(*bytes.Buffer)
+	bodyBuf.Reset()
+	defer bufPool.Put(bodyBuf)
+
+	err := c.readFileToBuffer(filename, bodyBuf)
+	if err != nil {
+		return "", err
+	}
+
+	return bodyBuf.String(), nil
+}
+
 // readFileToBuffer reads a file into a buffer
 func (c TemplateContext) readFileToBuffer(filename string, bodyBuf *bytes.Buffer) error {
 	if c.Root == nil {
@@ -315,7 +333,7 @@ func (TemplateContext) funcMarkdown(input any) (string, error) {
 			extension.Footnote,
 			highlighting.NewHighlighting(
 				highlighting.WithFormatOptions(
-					html.WithClasses(true),
+					chromahtml.WithClasses(true),
 				),
 			),
 		),


Test patch: