diff --git a/cmd/scriggo/help.go b/cmd/scriggo/help.go
index 6acd906ee..4653defc8 100644
--- a/cmd/scriggo/help.go
+++ b/cmd/scriggo/help.go
@@ -269,20 +269,27 @@ usage: scriggo serve [-S n] [--metrics] [--disable-livereload] [-const name=valu
Serve runs a web server and serves the template rooted at the current
directory. It is useful to learn Scriggo templates.
-It renders HTML and Markdown files based on file extension.
+Path resolution rules:
-For example:
+ /path/name.html
+ renders 'path/name.html' as an HTML template.
- http://localhost:8080/article
+ /path/name.md
+ serves the 'path/name.md' file as-is (without rendering it to HTML).
-it renders the file 'article.html' as HTML if exists, otherwise renders the
-file 'article.md' as Markdown.
+ /path/name
+ renders 'path/name.html' if it exists; otherwise, if 'path/name.md'
+ exists, renders it as Markdown and wraps it in a full HTML page.
-Serving a URL terminating with a slash:
+ /path/ and /path
+ render 'path/index' using the previous rule.
- http://localhost:8080/blog/
+Example:
-it renders 'blog/index.html' or 'blog/index.md'.
+ http://localhost:8080/article
+
+it renders the file 'article.html' as HTML if exists, otherwise renders the
+file 'article.md' as Markdown wrapped in HTML.
Markdown is converted to HTML with the Goldmark parser with the options
html.WithUnsafe, parser.WithAutoHeadingID and extension.GFM.
diff --git a/cmd/scriggo/serve.go b/cmd/scriggo/serve.go
index eb138bd1b..3448be5a7 100644
--- a/cmd/scriggo/serve.go
+++ b/cmd/scriggo/serve.go
@@ -296,7 +296,7 @@ func (srv *server) serveTemplate(w http.ResponseWriter, r *http.Request) {
}
return
}
- } else if ext != ".html" && ext != ".md" {
+ } else if ext != ".html" {
srv.static.ServeHTTP(w, r)
return
}
@@ -366,32 +366,33 @@ func (srv *server) serveTemplate(w http.ResponseWriter, r *http.Request) {
}
runTime := time.Since(start)
- s := b.Bytes()
- i := indexEndBody(s)
- if i == -1 {
- i = len(s)
- }
w.Header().Set("Content-Type", "text/html; charset=utf-8")
- if srv.liveReloads == nil {
- _, _ = w.Write(s)
+
+ if template.Format() == scriggo.FormatMarkdown {
+ md := goldmark.New(goldmarkOptions...)
+ var body bytes.Buffer
+ err = md.Convert(b.Bytes(), &body)
+ if err != nil {
+ http.Error(w, err.Error(), 500)
+ }
+ var urlPath string
+ if srv.liveReloads != nil {
+ urlPath = r.URL.Path
+ }
+ writeWrappedBody(w, urlPath, name, &body)
} else {
- _, _ = w.Write(s[:i])
- _, _ = io.WriteString(w, ``)
- _, _ = w.Write(s[i:])
+ _, _ = w.Write(s[:i])
+ _, _ = writeLiveReloadScript(w, r.URL.Path)
+ _, _ = w.Write(s[i:])
+ }
}
if srv.metrics.active {
@@ -513,3 +514,81 @@ func isBodyTag(s []byte) bool {
}
return false
}
+
+// writeWrappedBody writes a complete HTML page to w. It inserts body between
+// the
tags. If urlPath is non-empty, it includes a live reload script
+// using urlPath. The title parameter specifies the page title.
+func writeWrappedBody(w io.Writer, urlPath, title string, body *bytes.Buffer) {
+ _, _ = io.WriteString(w, `
+
+
+
+
+ `)
+ _, _ = io.WriteString(w, title)
+ _, _ = io.WriteString(w, `
+
+
+
+`)
+ _, _ = body.WriteTo(w)
+ if urlPath != "" {
+ _, _ = writeLiveReloadScript(w, urlPath)
+ }
+ _, _ = io.WriteString(w, `
+
+`)
+}
+
+// writeLiveReloadScript writes a live reload script for urlPath to w.
+// It returns the number of bytes written and any error.
+func writeLiveReloadScript(w io.Writer, urlPath string) (int, error) {
+ n, err := io.WriteString(w, ``)
+ return n, err
+}
diff --git a/internal/compiler/checker.go b/internal/compiler/checker.go
index 31550fe89..b5af8a26a 100644
--- a/internal/compiler/checker.go
+++ b/internal/compiler/checker.go
@@ -84,6 +84,7 @@ func typecheck(tree *ast.Tree, importer native.Importer, opts checkerOptions) (m
compilation.extendedTrees[extends.Tree.Path] = true
tree.Nodes = append([]ast.Node{dummyImport}, extends.Tree.Nodes...)
tree.Path = extends.Tree.Path
+ tree.Format = extends.Tree.Format
tc.path = extends.Tree.Path
}
diff --git a/internal/runtime/run.go b/internal/runtime/run.go
index ddf7ec857..f166d1ea4 100644
--- a/internal/runtime/run.go
+++ b/internal/runtime/run.go
@@ -1585,7 +1585,7 @@ func (vm *VM) run() (Addr, bool) {
if b == ReturnString {
out := vm.renderer.Out().(*strings.Builder)
vm.setString(1, out.String())
- } else if fn.Format == ast.FormatMarkdown && ast.Format(b) == ast.FormatHTML {
+ } else if vm.fn.Format == ast.FormatMarkdown && ast.Format(b) == ast.FormatHTML {
out := vm.renderer.Out().(*bytes.Buffer)
err := vm.env.conv(out.Bytes(), call.renderer.out)
if err != nil {
diff --git a/templates.go b/templates.go
index 9dfadb111..55eae3bf5 100644
--- a/templates.go
+++ b/templates.go
@@ -174,6 +174,11 @@ func (t *Template) Disassemble(n int) []byte {
return assemblies["main"]
}
+// Format returns the output format of the template when executed.
+func (t *Template) Format() Format {
+ return Format(t.fn.Format)
+}
+
// UsedVars returns the names of the global variables used in the template.
// A variable used in dead code may not be returned as used.
func (t *Template) UsedVars() []string {
diff --git a/templates_test.go b/templates_test.go
index bbb477fbf..af2830f4a 100644
--- a/templates_test.go
+++ b/templates_test.go
@@ -6,6 +6,7 @@ package scriggo
import (
"fmt"
+ "io/fs"
"reflect"
"strings"
"testing"
@@ -190,6 +191,78 @@ func TestFormatFS(t *testing.T) {
}
}
+func TestTemplateMainFormatWithExtends(t *testing.T) {
+ fsys := fstest.Files{
+ "index.md": `{% extends "layout.html" %}`,
+ "layout.html": `Title
`,
+ }
+ template, err := BuildTemplate(fsys, "index.md", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if template.fn.Format != ast.FormatHTML {
+ t.Fatalf("expected main format %s, got %s", ast.FormatHTML, template.fn.Format)
+ }
+}
+
+func TestTemplateFormat(t *testing.T) {
+ tests := map[string]struct {
+ fsys fs.FS
+ name string
+ expected Format
+ }{
+ "Text": {
+ fsys: fstest.Files{"index.txt": `hello`},
+ name: "index.txt",
+ expected: FormatText,
+ },
+ "HTML": {
+ fsys: fstest.Files{"index.html": `hello
`},
+ name: "index.html",
+ expected: FormatHTML,
+ },
+ "Markdown": {
+ fsys: fstest.Files{"index.md": `# hello`},
+ name: "index.md",
+ expected: FormatMarkdown,
+ },
+ "MarkdownExtendsHTML": {
+ fsys: fstest.Files{
+ "index.md": `{% extends "layout.html" %}`,
+ "layout.html": `hello
`,
+ },
+ name: "index.md",
+ expected: FormatHTML,
+ },
+ "MultipleExtendsUseFinalFormat": {
+ fsys: fstest.Files{
+ "index.md": `{% extends "level1.html" %}`,
+ "level1.html": `{% extends "level2.html" %}`,
+ "level2.html": `hello
`,
+ },
+ name: "index.md",
+ expected: FormatHTML,
+ },
+ "FormatFS": {
+ fsys: testFormatFS{Files: fstest.Files{"index": `hello`}, format: FormatJSON},
+ name: "index",
+ expected: FormatJSON,
+ },
+ }
+
+ for name, tc := range tests {
+ t.Run(name, func(t *testing.T) {
+ template, err := BuildTemplate(tc.fsys, tc.name, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got := template.Format(); got != tc.expected {
+ t.Fatalf("expected %s, got %s", tc.expected, got)
+ }
+ })
+ }
+}
+
// TestUnexpandedTransformer ensures the transformer runs before expansion.
func TestUnexpandedTransformer(t *testing.T) {
fsys := fstest.Files{
diff --git a/test/misc/multi_file_template_test.go b/test/misc/multi_file_template_test.go
index bbdf860e0..242249c0f 100644
--- a/test/misc/multi_file_template_test.go
+++ b/test/misc/multi_file_template_test.go
@@ -3256,6 +3256,20 @@ func TestMultiFileTemplate(t *testing.T) {
expectedOut: "--- start Markdown ---\n**bold**--- end Markdown ---\n",
},
+ "Show markdown macro in an HTML context": {
+ sources: fstest.Files{
+ "index.html": `{% macro M markdown %}# Hi{% end %}{{ M() }}`,
+ },
+ expectedOut: "--- start Markdown ---\n# Hi--- end Markdown ---\n",
+ },
+
+ "Show markdown macro in an HTML context - Indirect": {
+ sources: fstest.Files{
+ "index.html": `{% macro M markdown %}# Hi{% end %}{% _ = &M %}{{ M() }}`,
+ },
+ expectedOut: "--- start Markdown ---\n# Hi--- end Markdown ---\n",
+ },
+
"Recursive macro call (1)": {
sources: fstest.Files{
"index.html": `{% macro m(i int) %}{{ i }}{% if i > 0 %}{{ m(i - 1) }}{% end if %}{% end macro %}{{ m(5) }}`,