diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1f0872c --- /dev/null +++ b/LICENSE @@ -0,0 +1,26 @@ +Copyright © 2009, Michael Stapelberg and contributors + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/cmd/tl8-render/tl8render.go b/cmd/tl8-render/tl8render.go new file mode 100644 index 0000000..2db9452 --- /dev/null +++ b/cmd/tl8-render/tl8render.go @@ -0,0 +1,251 @@ +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "strings" + + "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 + + translatedVersion string +} + +// Kind implements Node.Kind +func (n *translationStatusNode) Kind() ast.NodeKind { + return kindTranslationStatus +} + +// Dump implements Node.Dump +func (n *translationStatusNode) Dump(source []byte, level int) { + ast.DumpHelper(n, source, level, nil, nil) +} + +var kindTranslationStatus = ast.NewNodeKind("TranslationStatus") + +type versionNode struct { + ast.BaseBlock + + since string +} + +// Kind implements Node.Kind +func (n *versionNode) Kind() ast.NodeKind { + return kindVersionNode +} + +// Dump implements Node.Dump +func (n *versionNode) Dump(source []byte, level int) { + ast.DumpHelper(n, source, level, nil, nil) +} + +var kindVersionNode = ast.NewNodeKind("VersionNode") + +type tl8transformer struct{} + +func modifyASTFromHeading(heading ast.Node) { + tsn := &translationStatusNode{} + vsn := &versionNode{} + for _, attr := range heading.Attributes() { + b, ok := attr.Value.([]byte) + if !ok { + continue + } + val := string(b) + if string(attr.Name) == "class" && strings.HasPrefix(val, "since_") { + vsn.since = strings.TrimPrefix(val, "since_") + } + if string(attr.Name) == "translated" { + tsn.translatedVersion = val + } + //log.Printf("heading attr, name=%s, value=%s", attr.Name, val) + } + + if vsn.since != "" { + // Adding a child will make it part of the heading HTML element + // (e.g.

) + heading.AppendChild(heading, vsn) + } + + if tsn.translatedVersion != "" { + // Insert the TranslationStatus node after the heading: + tsn.SetNextSibling(heading.NextSibling()) + heading.SetNextSibling(tsn) + } +} + +// Transform is called once per document. +func (t *tl8transformer) Transform(doc *ast.Document, reader text.Reader, pc parser.Context) { + ast.Walk(doc, func(n ast.Node, entering bool) (ast.WalkStatus, error) { + //log.Printf("ast.Walk(type=%v, kind=%v)", n.Type(), n.Kind()) + if n.Type() == ast.TypeDocument { + return ast.WalkContinue, nil + } + if n.Kind() == ast.KindHeading && entering { + modifyASTFromHeading(n) + } + return ast.WalkSkipChildren, nil + }) +} + +func numericVersionToHuman(v string) string { + return strings.ReplaceAll(v, "_", ".") +} + +type tl8renderer struct{} + +func (r *tl8renderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { + reg.Register(kindTranslationStatus, r.renderTranslationStatus) + reg.Register(kindVersionNode, r.renderVersion) +} + +func (r *tl8renderer) renderVersion(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { + if !entering { + return ast.WalkContinue, nil + } + vsn := node.(*versionNode) + fmt.Fprintf(w, `since i3 v%s`, numericVersionToHuman(vsn.since)) + return ast.WalkContinue, nil +} + +func (r *tl8renderer) renderTranslationStatus(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { + if !entering { + return ast.WalkContinue, nil + } + tsn := node.(*translationStatusNode) + + // NOTE: Ideally we would link to a list of commits for the relevant file + // between the last-translated version and the current version. + // Unfortunately, GitHub does not seem to provide such a view. + + translatedVersion := numericVersionToHuman(tsn.translatedVersion) + fmt.Fprintf(w, ` +Out-of-date! This section’s translation was last updated for i3 v%s +(what changed?) +(contribute) +`, + translatedVersion, + "userguide", /* TODO */ + "userguide" /* TODO */) + return ast.WalkContinue, nil +} + +func render1(fn string) error { + outfn := strings.TrimSuffix(fn, filepath.Ext(fn)) + ".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)), + ), + ) + + out, err := renameio.TempFile("", outfn) + if err != nil { + return err + } + + out.Write([]byte(preamble)) + + if err := md.Convert(source, out); err != nil { + return err + } + + out.Write([]byte(footer)) + + if err := out.CloseAtomicallyReplace(); err != nil { + return err + } + return nil +} + +func tl8render() error { + flag.Parse() + if flag.NArg() < 1 { + return fmt.Errorf("syntax: %s [...]", filepath.Base(os.Args[0])) + } + + for _, fn := range flag.Args() { + if err := render1(fn); err != nil { + return err + } + } + + return nil +} + +func main() { + if err := tl8render(); err != nil { + log.Fatal(err) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..37fa9e4 --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module translation + +go 1.15 + +require ( + github.com/Kunde21/markdownfmt/v2 v2.0.3 + github.com/google/renameio v1.0.0 + github.com/yuin/goldmark v1.3.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e7f2ec0 --- /dev/null +++ b/go.sum @@ -0,0 +1,9 @@ +github.com/Kunde21/markdownfmt/v2 v2.0.3 h1:8Xs3GX5hVK0+AiVTXb2xvr8zvrl8Z8DQXOqmA6sXhjk= +github.com/Kunde21/markdownfmt/v2 v2.0.3/go.mod h1:50JNMOFTYtR8g1f+U8BZlw0M9RL5ZUqjOxxTgITeyrg= +github.com/google/renameio v1.0.0 h1:xhp2CnJmgQmpJU4RY8chagahUq5mbPPAbiSQstKpVMA= +github.com/google/renameio v1.0.0/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk= +github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/yuin/goldmark v1.1.24/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.1 h1:eVwehsLsZlCJCwXyGLgg+Q4iFWE/eTIMG0e8waCmm/I= +github.com/yuin/goldmark v1.3.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=