diff --git a/cmd/tl8-render/tl8render.go b/cmd/tl8-render/tl8render.go
index 2db9452..92c1384 100644
--- a/cmd/tl8-render/tl8render.go
+++ b/cmd/tl8-render/tl8render.go
@@ -3,62 +3,22 @@ package main
import (
"flag"
"fmt"
+ "html/template"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
+ "translation/internal/tl8"
"github.com/google/renameio"
- "github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
- "github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer"
- "github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
)
-const preamble = `
-
-
-
-
-
-i3: i3 User’s Guide
-
-
-
-
-
-
-
-
-`
-
-const footer = `
-
-
-`
-
type translationStatusNode struct {
ast.BaseBlock
@@ -80,7 +40,7 @@ var kindTranslationStatus = ast.NewNodeKind("TranslationStatus")
type versionNode struct {
ast.BaseBlock
- since string
+ introduced string
}
// Kind implements Node.Kind
@@ -106,8 +66,8 @@ func modifyASTFromHeading(heading ast.Node) {
continue
}
val := string(b)
- if string(attr.Name) == "class" && strings.HasPrefix(val, "since_") {
- vsn.since = strings.TrimPrefix(val, "since_")
+ if string(attr.Name) == "introduced" {
+ vsn.introduced = val
}
if string(attr.Name) == "translated" {
tsn.translatedVersion = val
@@ -115,7 +75,7 @@ func modifyASTFromHeading(heading ast.Node) {
//log.Printf("heading attr, name=%s, value=%s", attr.Name, val)
}
- if vsn.since != "" {
+ if vsn.introduced != "" {
// Adding a child will make it part of the heading HTML element
// (e.g. )
heading.AppendChild(heading, vsn)
@@ -146,7 +106,9 @@ func numericVersionToHuman(v string) string {
return strings.ReplaceAll(v, "_", ".")
}
-type tl8renderer struct{}
+type tl8renderer struct {
+ basename string
+}
func (r *tl8renderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(kindTranslationStatus, r.renderTranslationStatus)
@@ -158,7 +120,7 @@ func (r *tl8renderer) renderVersion(w util.BufWriter, source []byte, node ast.No
return ast.WalkContinue, nil
}
vsn := node.(*versionNode)
- fmt.Fprintf(w, `since i3 v%s`, numericVersionToHuman(vsn.since))
+ fmt.Fprintf(w, `since i3 v%s`, numericVersionToHuman(vsn.introduced))
return ast.WalkContinue, nil
}
@@ -177,51 +139,59 @@ func (r *tl8renderer) renderTranslationStatus(w util.BufWriter, source []byte, n
Out-of-date! This section’s translation was last updated for i3 v%s
(what changed?)
(contribute)
-`,
+
+`,
translatedVersion,
- "userguide", /* TODO */
- "userguide" /* TODO */)
+ r.basename,
+ r.basename)
return ast.WalkContinue, nil
}
-func render1(fn string) error {
- outfn := strings.TrimSuffix(fn, filepath.Ext(fn)) + ".html"
+func render1(fn string, headerTmpl, footerTmpl *template.Template) error {
+ basename := strings.TrimSuffix(fn, filepath.Ext(fn))
+ outfn := basename + ".html"
source, err := ioutil.ReadFile(fn)
if err != nil {
return err
}
- md := goldmark.New(
- // GFM is GitHub Flavored Markdown, which we need for tables, for
- // example.
- goldmark.WithExtensions(extension.GFM),
- goldmark.WithParserOptions(
- parser.WithAutoHeadingID(),
- // The Attribute option allows us to id, classes, and arbitrary
- // options on headings (for translation status).
- parser.WithAttribute(),
- parser.WithASTTransformers(util.Prioritized(&tl8transformer{}, 1)),
- ),
- goldmark.WithRendererOptions(
- html.WithHardWraps(),
- html.WithXHTML(),
- renderer.WithNodeRenderers(
- util.Prioritized(&tl8renderer{}, 500)),
- ),
- )
+ md := tl8.NewGoldmarkWithOptions(
+ []parser.Option{parser.WithASTTransformers(util.Prioritized(&tl8transformer{}, 1))},
+ []renderer.Option{renderer.WithNodeRenderers(util.Prioritized(&tl8renderer{
+ basename: basename,
+ }, 500))})
out, err := renameio.TempFile("", outfn)
if err != nil {
return err
}
- out.Write([]byte(preamble))
+ if headerTmpl != nil {
+ doc, err := tl8.Segment(source)
+ if err != nil {
+ return err
+ }
+
+ documentHeading := doc.Headings[0]
+
+ if err := headerTmpl.Execute(out, struct {
+ Title string
+ }{
+ Title: documentHeading.Text,
+ }); err != nil {
+ return fmt.Errorf("rendering -header_template: %v", err)
+ }
+ }
if err := md.Convert(source, out); err != nil {
return err
}
- out.Write([]byte(footer))
+ if footerTmpl != nil {
+ if err := footerTmpl.Execute(out, nil); err != nil {
+ return fmt.Errorf("rendering -footer_template: %v", err)
+ }
+ }
if err := out.CloseAtomicallyReplace(); err != nil {
return err
@@ -230,13 +200,40 @@ func render1(fn string) error {
}
func tl8render() error {
+ var (
+ header = flag.String("header_template",
+ "",
+ "path to a Go template file (https://golang.org/pkg/html/template/) containing the HTML that should be printed before converted markdown content")
+
+ footer = flag.String("footer_template",
+ "",
+ "path to a Go template file (https://golang.org/pkg/html/template/) containing the HTML that should be printed after converted markdown content")
+ )
+
flag.Parse()
if flag.NArg() < 1 {
return fmt.Errorf("syntax: %s [...]", filepath.Base(os.Args[0]))
}
+ var headerTmpl, footerTmpl *template.Template
+ if *header != "" {
+ var err error
+ headerTmpl, err = template.ParseFiles(*header)
+ if err != nil {
+ return err
+ }
+ }
+
+ if *footer != "" {
+ var err error
+ footerTmpl, err = template.ParseFiles(*footer)
+ if err != nil {
+ return err
+ }
+ }
+
for _, fn := range flag.Args() {
- if err := render1(fn); err != nil {
+ if err := render1(fn, headerTmpl, footerTmpl); err != nil {
return err
}
}
diff --git a/cmd/tl8-render/tl8render_test.go b/cmd/tl8-render/tl8render_test.go
new file mode 100644
index 0000000..d93d793
--- /dev/null
+++ b/cmd/tl8-render/tl8render_test.go
@@ -0,0 +1,92 @@
+package main
+
+import (
+ "bytes"
+ "html/template"
+ "io/ioutil"
+ "path/filepath"
+ "testing"
+ "translation/internal/tl8"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/yuin/goldmark/parser"
+ "github.com/yuin/goldmark/renderer"
+ "github.com/yuin/goldmark/util"
+)
+
+func TestRenderVersion(t *testing.T) {
+ md := tl8.NewGoldmarkWithOptions(
+ []parser.Option{parser.WithASTTransformers(util.Prioritized(&tl8transformer{}, 1))},
+ []renderer.Option{renderer.WithNodeRenderers(util.Prioritized(&tl8renderer{}, 500))})
+ source := []byte(`# heading {#heading_id introduced="4_16"}`)
+ var buf bytes.Buffer
+ if err := md.Convert(source, &buf); err != nil {
+ t.Fatal(err)
+ }
+ got := buf.String()
+ want := `headingsince i3 v4.16
+`
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Fatalf("unexpected output: diff (-want +got):\n%s", diff)
+ }
+}
+
+func TestRenderTranslation(t *testing.T) {
+ md := tl8.NewGoldmarkWithOptions(
+ []parser.Option{parser.WithASTTransformers(util.Prioritized(&tl8transformer{}, 1))},
+ []renderer.Option{renderer.WithNodeRenderers(util.Prioritized(&tl8renderer{
+ basename: "userguide",
+ }, 500))})
+ source := []byte(`# heading {#heading_id translated="4_17"}`)
+ var buf bytes.Buffer
+ if err := md.Convert(source, &buf); err != nil {
+ t.Fatal(err)
+ }
+ got := buf.String()
+ want := `heading
+
+Out-of-date! This section’s translation was last updated for i3 v4.17
+(what changed?)
+(contribute)
+
+`
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Fatalf("unexpected output: diff (-want +got):\n%s", diff)
+ }
+}
+
+func TestRenderTemplate(t *testing.T) {
+ headerTmpl, err := template.New("").Parse(`
+
+ {{ .Title }}
+
+
+`)
+ if err != nil {
+ t.Fatal(err)
+ }
+ tmpDir := t.TempDir()
+ fn := filepath.Join(tmpDir, "userguide.markdown")
+ const userguideMarkdown = `# i3 User Guide
+`
+ if err := ioutil.WriteFile(fn, []byte(userguideMarkdown), 0644); err != nil {
+ t.Fatal(err)
+ }
+ if err := render1(fn, headerTmpl, nil); err != nil {
+ t.Fatal(err)
+ }
+ got, err := ioutil.ReadFile(filepath.Join(tmpDir, "userguide.html"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ const want = `
+
+ i3 User Guide
+
+
+i3 User Guide
+`
+ if diff := cmp.Diff(string(want), string(got)); diff != "" {
+ t.Fatalf("RenderTemplate: unexpected diff (-want +got):\n%s", diff)
+ }
+}
diff --git a/internal/tl8/tl8.go b/internal/tl8/tl8.go
index 9cf7eb1..a5dfbd7 100644
--- a/internal/tl8/tl8.go
+++ b/internal/tl8/tl8.go
@@ -10,25 +10,30 @@ import (
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
+ "github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/text"
)
func NewGoldmark() goldmark.Markdown {
+ return NewGoldmarkWithOptions(nil, nil)
+}
+
+func NewGoldmarkWithOptions(parserOptions []parser.Option, rendererOptions []renderer.Option) goldmark.Markdown {
return goldmark.New(
// GFM is GitHub Flavored Markdown, which we need for tables, for
// example.
goldmark.WithExtensions(extension.GFM),
- goldmark.WithParserOptions(
+ goldmark.WithParserOptions(append([]parser.Option{
parser.WithAutoHeadingID(),
// The Attribute option allows us to id, classes, and arbitrary
// options on headings (for translation status).
parser.WithAttribute(),
- ),
- goldmark.WithRendererOptions(
+ }, parserOptions...)...),
+ goldmark.WithRendererOptions(append([]renderer.Option{
html.WithHardWraps(),
html.WithXHTML(),
- ),
+ }, rendererOptions...)...),
)
}
@@ -36,6 +41,7 @@ type Heading struct {
Line int
ID string
Translated string
+ Text string
}
type Section struct {
@@ -107,6 +113,7 @@ func Segment(source []byte) (*Document, error) {
return ast.WalkStop, fmt.Errorf("BUG: could not find line offset for position %d", first.Start)
}
h.Line = line + 1
+ h.Text = string(n.Text(source))
headings = append(headings, h)
headingsByID[h.ID] = h
}