From c7d779c03f3dd66dbfed7e4d86dd337c999f5c6d Mon Sep 17 00:00:00 2001 From: Dominik Menke Date: Thu, 14 Jul 2022 22:55:37 +0200 Subject: [PATCH] docs: switch markdown renderer [wip] TODO: link targets need to be rewritten ("./foo.md" works on GitHub, but we'll need "/docs/foo"). Also, the path prefix should be injected into Handler(), which means deferring the markdown rendering to the start of the server. --- docs/{index.md => README.md} | 0 docs/docs.go | 170 +++++++++++++++++++++-------------- docs/docs.html | 39 ++++++++ docs/docs.yml | 2 +- go.mod | 10 ++- go.sum | 22 +++-- service/assets/texd.js | 2 +- service/ui.go | 6 +- service/ui.html | 17 +++- 9 files changed, 185 insertions(+), 83 deletions(-) rename docs/{index.md => README.md} (100%) create mode 100644 docs/docs.html diff --git a/docs/index.md b/docs/README.md similarity index 100% rename from docs/index.md rename to docs/README.md diff --git a/docs/docs.go b/docs/docs.go index b83996b..7cf477e 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -3,126 +3,162 @@ package docs import ( "bytes" "embed" - "fmt" + "html/template" "io" + "log" "net/http" "strings" - "github.com/Depado/bfchroma/v2" - "github.com/alecthomas/chroma/v2/formatters/html" - bf "github.com/russross/blackfriday/v2" + toc "github.com/abhinav/goldmark-toc" + chromahtml "github.com/alecthomas/chroma/formatters/html" + "github.com/digineo/texd" + "github.com/microcosm-cc/bluemonday" + "github.com/yuin/goldmark" + highlighting "github.com/yuin/goldmark-highlighting" + "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/parser" + "github.com/yuin/goldmark/renderer/html" + "github.com/yuin/goldmark/text" "gopkg.in/yaml.v3" ) -//go:embed docs.yml *.md **/*.md +//go:embed *.md **/*.md var sources embed.FS +//go:embed docs.yml +var config []byte + +//go:embed docs.html +var rawLayout string +var tplLayout = template.Must(template.New("layout").Parse(rawLayout)) + type page struct { Title string Breadcrumbs []string - Body string + TOC *toc.TOC + CSS []byte + Body []byte File string Route string Children []*page } +type pageRoutes map[string]*page -var root = func() page { - structure, err := sources.Open("docs.yml") - if err != nil { - panic(err) - } - defer structure.Close() - +var root, routes = func() (page, pageRoutes) { var menu page - dec := yaml.NewDecoder(structure) + dec := yaml.NewDecoder(bytes.NewReader(config)) dec.KnownFields(true) if err := dec.Decode(&menu); err != nil { panic(err) } - menu.init() - return menu + r := make(pageRoutes) + menu.init(r) + return menu, r }() -func (pg *page) init(crumbs ...string) { +func (pg *page) init(r pageRoutes, crumbs ...string) { if pg.File != "" { - if r := strings.TrimSuffix(pg.File, ".md"); r == "index" { + if r := strings.TrimSuffix(pg.File, ".md"); r == "README" { pg.Route = "" } else { pg.Route = "/" + r } - + r[pg.Route] = pg pg.parseFile() } if pg.Title != "" { - pg.Breadcrumbs = append(pg.Breadcrumbs, pg.Title) + pg.Breadcrumbs = append([]string{pg.Title}, crumbs...) } for _, child := range pg.Children { - child.init(pg.Breadcrumbs...) + child.init(r, pg.Breadcrumbs...) } } +var sanitize = func() func(io.Reader) *bytes.Buffer { + p := bluemonday.UGCPolicy() + p.AllowAttrs("class").Globally() + return p.SanitizeReader +}() + func (pg *page) parseFile() { - body, err := sources.ReadFile(pg.File) + raw, err := sources.ReadFile(pg.File) if err != nil { panic(err) } - r := bfchroma.NewRenderer( - bfchroma.WithoutAutodetect(), - bfchroma.ChromaOptions( - html.WithLineNumbers(true), + var css, body bytes.Buffer + md := goldmark.New( + goldmark.WithParserOptions( + parser.WithAutoHeadingID(), + ), + goldmark.WithRendererOptions( + html.WithUnsafe(), + ), + goldmark.WithExtensions( + extension.GFM, + highlighting.NewHighlighting( + highlighting.WithCSSWriter(&css), + highlighting.WithStyle("github"), + highlighting.WithFormatOptions( + chromahtml.WithLineNumbers(true), + chromahtml.WithClasses(true), + ), + ), ), - bfchroma.Extend(bf.NewHTMLRenderer(bf.HTMLRendererParameters{ - Flags: bf.CommonHTMLFlags & ^bf.UseXHTML & ^bf.CompletePage, - })), - ) - parser := bf.New( - bf.WithExtensions(bf.CommonExtensions), - bf.WithRenderer(r), ) - ast := parser.Parse(body) - var buf bytes.Buffer - var inH1 bool - - ast.Walk(func(node *bf.Node, entering bool) bf.WalkStatus { - switch node.Type { - case bf.Heading: - inH1 = entering && node.HeadingData.Level == 1 && pg.Title == "" - case bf.Text: - if inH1 { - pg.Title = string(node.Literal) - } - case bf.Link: - if entering && bytes.HasPrefix(node.LinkData.Destination, []byte("./")) { - node.LinkData.Destination = bytes.TrimSuffix(node.LinkData.Destination, []byte(".md")) - } + doc := md.Parser().Parse(text.NewReader(raw)) + tree, err := toc.Inspect(doc, raw) + if err != nil { + panic(err) + } + if pg.Title == "" { + if len(tree.Items) > 0 { + pg.Title = string(tree.Items[0].Title) } - return r.RenderNode(&buf, node, entering) - }) - - pg.Body = buf.String() -} - -func (pg *page) Dump(w io.Writer) { - fmt.Fprintf(w, "- %s (%s)\n", pg.Title, pg.Route) - fmt.Fprintln(w, pg.Body) - fmt.Fprintln(w) - - for _, c := range pg.Children { - c.Dump(w) } + if err := md.Renderer().Render(&body, raw, doc); err != nil { + panic(err) + } + pg.TOC = tree + pg.CSS = css.Bytes() + pg.Body = sanitize(&body).Bytes() } func Handler() http.Handler { + type pageVars struct { + Version string + Title string + CSS template.CSS + Content template.HTML + } + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.Header().Set("X-Content-Type-Options", "nosniff") - w.WriteHeader(http.StatusOK) + pg := routes[r.URL.Path] + if pg == nil { + http.NotFound(w, r) + return + } - fmt.Fprintf(w, "%#v\n\n", r.URL) + var buf bytes.Buffer + err := tplLayout.Execute(&buf, &pageVars{ + Version: texd.Version(), + Title: strings.Join(pg.Breadcrumbs, " ยท "), + CSS: template.CSS(pg.CSS), + Content: template.HTML(pg.Body), + }) + + if err != nil { + log.Println(err) + code := http.StatusInternalServerError + http.Error(w, http.StatusText(code), code) + return + } - root.Dump(w) + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.Header().Set("X-Content-Type-Options", "nosniff") + w.WriteHeader(http.StatusOK) + _, _ = buf.WriteTo(w) }) } diff --git a/docs/docs.html b/docs/docs.html new file mode 100644 index 0000000..d864afc --- /dev/null +++ b/docs/docs.html @@ -0,0 +1,39 @@ + + + + + + + {{ .Title }} + + + + + + +
+ + +
+ {{ .Content }} +
+
+ + diff --git a/docs/docs.yml b/docs/docs.yml index 34ab7c8..616fd06 100644 --- a/docs/docs.yml +++ b/docs/docs.yml @@ -1,5 +1,5 @@ --- -file: index.md +file: README.md children: - file: operation-modes.md - file: cli-options.md diff --git a/go.mod b/go.mod index f52c3a2..b665d46 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,24 @@ module github.com/digineo/texd go 1.18 require ( - github.com/Depado/bfchroma/v2 v2.0.0 - github.com/alecthomas/chroma/v2 v2.2.0 + github.com/abhinav/goldmark-toc v0.2.1 + github.com/alecthomas/chroma v0.10.0 github.com/bahlo/generic-list-go v0.2.0 github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d github.com/docker/docker v20.10.17+incompatible github.com/docker/go-units v0.4.0 github.com/gorilla/handlers v1.5.1 github.com/gorilla/mux v1.8.0 + github.com/microcosm-cc/bluemonday v1.0.19 github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 github.com/opencontainers/image-spec v1.0.2 github.com/prometheus/client_golang v1.12.2 - github.com/russross/blackfriday/v2 v2.1.0 github.com/spf13/afero v1.8.2 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.0 github.com/thediveo/enumflag v0.10.1 + github.com/yuin/goldmark v1.4.13 + github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 go.uber.org/zap v1.21.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -26,6 +28,7 @@ require ( require ( github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Microsoft/go-winio v0.5.2 // indirect + github.com/aymerick/douceur v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -36,6 +39,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.6 // indirect + github.com/gorilla/css v1.0.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/morikuni/aec v1.0.0 // indirect diff --git a/go.sum b/go.sum index 40dcd76..0e552d7 100644 --- a/go.sum +++ b/go.sum @@ -40,20 +40,21 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOEl github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Depado/bfchroma/v2 v2.0.0 h1:IRpN9BPkNwEpR6w1ectIcNWOuhDSLx+8f1pn83fzxx8= -github.com/Depado/bfchroma/v2 v2.0.0/go.mod h1:wFwW/Pw8Tnd0irzgO9Zxtxgzp3aPS8qBWlyadxujxmw= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/alecthomas/chroma/v2 v2.2.0 h1:Aten8jfQwUqEdadVFFjNyjx7HTexhKP0XuqBG67mRDY= -github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= -github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae h1:zzGwJfFlFGD94CyyYwCJeSuD32Gj9GTaSi5y9hoVzdY= +github.com/abhinav/goldmark-toc v0.2.1 h1:QJsKKGbdVeCWYMB11hSkNuZLuIzls7Y4KBZfwTkBB90= +github.com/abhinav/goldmark-toc v0.2.1/go.mod h1:aq1IZ9qN85uFYpowec98iJrFkEHYT4oeFD1SC0qd8d0= +github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= +github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= @@ -195,6 +196,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= @@ -239,6 +242,8 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/microcosm-cc/bluemonday v1.0.19 h1:OI7hoF5FY4pFz2VA//RN8TfM0YJ2dJcl4P4APrCWy6c= +github.com/microcosm-cc/bluemonday v1.0.19/go.mod h1:QNzV2UbLK2/53oIIwTOyLUSABMkjZ4tqiyC1g/DyqxE= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= @@ -310,7 +315,6 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -352,7 +356,13 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.3/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.5/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg= +github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594 h1:yHfZyN55+5dp1wG7wDKv8HQ044moxkyGq12KFFMFDxg= +github.com/yuin/goldmark-highlighting v0.0.0-20220208100518-594be1970594/go.mod h1:U9ihbh+1ZN7fR5Se3daSPoz1CGF9IYtSvWwVQtnzGHU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= diff --git a/service/assets/texd.js b/service/assets/texd.js index e683444..8bad096 100644 --- a/service/assets/texd.js +++ b/service/assets/texd.js @@ -65,7 +65,7 @@ const app = Vue.createApp({ }, beforeMount() { - setInterval(this.fetchStatus, 1000) + setInterval(this.fetchStatus, 5000) this.fetchStatus() }, diff --git a/service/ui.go b/service/ui.go index c743828..82b7b08 100644 --- a/service/ui.go +++ b/service/ui.go @@ -3,6 +3,7 @@ package service import ( "bytes" "embed" + "io" "net/http" ) @@ -13,12 +14,11 @@ var uiHTML []byte var assets embed.FS func HandleUI(res http.ResponseWriter, req *http.Request) { - buf := bytes.NewBuffer(uiHTML) - res.Header().Set("Content-Type", mimeTypeHTML) res.Header().Set("X-Content-Type-Options", "nosniff") res.WriteHeader(http.StatusOK) - _, _ = buf.WriteTo(res) + + _, _ = io.Copy(res, bytes.NewReader(uiHTML)) } func HandleAssets() http.Handler { diff --git a/service/ui.html b/service/ui.html index 507a4ee..fa32787 100644 --- a/service/ui.html +++ b/service/ui.html @@ -13,9 +13,18 @@
-