Skip to content

Commit

Permalink
v0.0.6
Browse files Browse the repository at this point in the history
  • Loading branch information
pilcrowOnPaper committed Dec 12, 2023
1 parent 2dc3f90 commit fbfa51a
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 105 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
bin
.env
.DS_Store
.DS_Store
malta
50 changes: 50 additions & 0 deletions assets/markdown.css
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,54 @@ main .codeblock a {
.codeblock .s1,
.codeblock .s2 {
color: rgb(228, 69, 58);
}

main .table-wrapper {
overflow: auto;
width: 100%;
white-space: nowrap;
padding-bottom: 1rem;
}

main table {
margin-top: 1rem;
margin-bottom: 0;
width: 100%;
text-align: left;
border-spacing: 0;
}

main thead,
main tbody {
width: 100%;
}

main td {
border-bottom: 1px solid rgb(221, 221, 221);
padding-top: 0.375rem;
padding-bottom: 0.375rem;
padding-left: 0.25rem;
padding-right: 0.25rem;
table-layout: auto;
}

main th {
font-weight: 500;
font-size: 0.875rem;
padding-top: 0.375rem;
padding-bottom: 0.375rem;
padding-left: 0.25rem;
padding-right: 0.25rem;
border-bottom: 1px solid rgb(44, 44, 44);
}

main blockquote {
margin: 0;
border-left: 2px solid rgb(110, 110, 110);
padding-left: 0.5rem;
color: rgb(110, 110, 110);
}

main blockquote>p {
margin: 0;
}
101 changes: 101 additions & 0 deletions code-block-links.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package main

import (
"bytes"
"fmt"
"strings"

"github.com/alecthomas/chroma"
"github.com/alecthomas/chroma/formatters/html"
"github.com/alecthomas/chroma/lexers"
"github.com/alecthomas/chroma/styles"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
)

type codeBlockLinksAstTransformer struct{}

func (a codeBlockLinksAstTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
walker := func(n ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
}
if n.Kind() != ast.KindFencedCodeBlock {
return ast.WalkContinue, nil
}
lineCount := n.Lines().Len()
defCount := 0
for i := 0; i < lineCount; i++ {
lineValue := string(reader.Value(n.Lines().At(i)))
if !strings.HasPrefix(lineValue, "//$") {
break
}
defCount += 1
keyValue := strings.Split(strings.TrimSpace(strings.Replace(lineValue, "//$", "", 1)), "=")
if len(keyValue) != 2 {
continue
}
n.SetAttribute([]byte("link:"+keyValue[0]), keyValue[1])
}
n.Lines().SetSliced(defCount, n.Lines().Len())
return ast.WalkContinue, nil
}
ast.Walk(node, walker)
}

type codeBlockLinksRenderer struct{}

func (r codeBlockLinksRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(ast.KindFencedCodeBlock, r.renderCustomCodeBlockLinks)
}

func (r codeBlockLinksRenderer) renderCustomCodeBlockLinks(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
if entering {
return ast.WalkContinue, nil
}
codeBlock := node.(*ast.FencedCodeBlock)

var content string
for i := 0; i < codeBlock.Lines().Len(); i++ {
line := codeBlock.Lines().At(i)
content += string(line.Value(source))
}
lexer := lexers.Get(string(codeBlock.Language(source)))
if lexer == nil {
w.WriteString("<pre class=\"codeblock\"><code>")
w.WriteString(content)
w.WriteString("</code></pre>")
return ast.WalkContinue, nil
}
lexer = chroma.Coalesce(lexer)

formatter := html.New(html.WithClasses(true), html.PreventSurroundingPre(true))

iterator, err := lexer.Tokenise(nil, content)
if err != nil {
return ast.WalkStop, err
}
buf := new(bytes.Buffer)
// random style
formatter.Format(buf, styles.GitHub, iterator)

html := buf.String()
for _, attribute := range node.Attributes() {
attributeName := string(attribute.Name)
if !strings.HasPrefix(attributeName, "link:") {
continue
}
target := strings.Replace(attributeName, "link:", "", 1)
dest := attribute.Value.(string)
html = strings.ReplaceAll(html, "$$"+target, fmt.Sprintf("<a href=\"%s\">%s</a>", dest, target))
}

w.WriteString("<pre class=\"codeblock\"><code>")
w.WriteString(html)
w.WriteString("</code></pre>")

return ast.WalkContinue, nil
}
4 changes: 0 additions & 4 deletions docs/pages/index.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
---
title: "Malta documentation"
---

# Malta documentation

Malta is a CLI tool to generate documentation sites with markdown files, just like this one. [Get started →](/basics/setup)
Expand Down
120 changes: 20 additions & 100 deletions malta.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,10 @@ import (
"strings"

"github.com/adrg/frontmatter"
"github.com/alecthomas/chroma"
"github.com/alecthomas/chroma/formatters/html"
"github.com/alecthomas/chroma/lexers"
"github.com/alecthomas/chroma/styles"
"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/text"
"github.com/yuin/goldmark/util"
)

Expand All @@ -31,6 +26,7 @@ var config struct {
Twitter string `json:"twitter"`
Sidebar []SidebarSectionConfig `json:"sidebar"`
}

var markdownFilePaths []string

//go:embed assets/template.html
Expand All @@ -43,27 +39,31 @@ var mainCss []byte
var markdownCss []byte

func main() {
os.Exit(run())
}

func run() int {
configJson, err := os.ReadFile("malta.config.json")
if err != nil {
if errors.Is(err, os.ErrNotExist) {
fmt.Println("Missing 'malta.config.json'")
return
return 1
}
panic(err)
}

json.Unmarshal(configJson, &config)
if config.Name == "" {
fmt.Println("Missing config: name")
return
return 1
}
if config.Domain == "" {
fmt.Println("Missing config: domain")
return
return 1
}
if config.Description == "" {
fmt.Println("Missing config: description")
return
return 1
}

navSections := []NavSection{}
Expand All @@ -80,14 +80,13 @@ func main() {
panic(err)
}

markdown := goldmark.New()
markdown.Parser().AddOptions(parser.WithASTTransformers(util.Prioritized(&astTransformer{}, 500)))
markdown.Renderer().AddOptions(renderer.WithNodeRenderers(util.Prioritized(&codeBlockLinkRenderer{}, 100)))
markdown := goldmark.New(goldmark.WithExtensions(extension.Table))
markdown.Parser().AddOptions(parser.WithASTTransformers(util.Prioritized(&codeBlockLinksAstTransformer{}, 500)))
markdown.Renderer().AddOptions(renderer.WithNodeRenderers(util.Prioritized(&codeBlockLinksRenderer{}, 100)))

os.RemoveAll("dist")

for _, markdownFilePath := range markdownFilePaths {
fmt.Println(markdownFilePath)
var matter struct {
Title string `yaml:"title"`
}
Expand All @@ -97,7 +96,7 @@ func main() {
pageMarkdown, _ := frontmatter.MustParse(markdownFile, &matter)
if matter.Title == "" {
fmt.Printf("Page %s missing attribute: title\n", markdownFilePath)
return
return 1
}

var markdownHtmlBuf bytes.Buffer
Expand All @@ -106,6 +105,10 @@ func main() {
panic(err)
}

markdownHtml := markdownHtmlBuf.String()
markdownHtml = strings.ReplaceAll(markdownHtml, "<table>", "<div class=\"table-wrapper\"><table>")
markdownHtml = strings.ReplaceAll(markdownHtml, "</table>", "</table></div>")

tmpl, _ := template.New("html").Parse(string(htmlTemplate))

dstPath := strings.Replace(strings.Replace(markdownFilePath, "pages", "dist", 1), ".md", ".html", 1)
Expand All @@ -123,7 +126,7 @@ func main() {
urlPathname = strings.Replace(urlPathname, "/index", "", 1)

err = tmpl.Execute(dstHtmlFile, Data{
Markdown: template.HTML(markdownHtmlBuf.String()),
Markdown: template.HTML(markdownHtml),
Name: config.Name,
Description: config.Description,
Url: config.Domain + urlPathname,
Expand All @@ -138,6 +141,7 @@ func main() {

os.WriteFile("dist/main.css", mainCss, os.ModePerm)
os.WriteFile("dist/markdown.css", markdownCss, os.ModePerm)
return 0
}

func walkPagesDir(path string, info os.FileInfo, err error) error {
Expand All @@ -151,90 +155,6 @@ func walkPagesDir(path string, info os.FileInfo, err error) error {
return nil
}

type astTransformer struct{}

func (a *astTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
walker := func(n ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
}
if n.Kind() != ast.KindFencedCodeBlock {
return ast.WalkContinue, nil
}
lineCount := n.Lines().Len()
defCount := 0
for i := 0; i < lineCount; i++ {
lineValue := string(reader.Value(n.Lines().At(i)))
if !strings.HasPrefix(lineValue, "//$") {
break
}
defCount += 1
keyValue := strings.Split(strings.TrimSpace(strings.Replace(lineValue, "//$", "", 1)), "=")
if len(keyValue) != 2 {
continue
}
n.SetAttribute([]byte("link:"+keyValue[0]), keyValue[1])
}
n.Lines().SetSliced(defCount, n.Lines().Len())
return ast.WalkContinue, nil
}
ast.Walk(node, walker)
}

type codeBlockLinkRenderer struct{}

func (r codeBlockLinkRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(ast.KindFencedCodeBlock, r.renderCustomCodeBlockLinks)
}

func (r codeBlockLinkRenderer) renderCustomCodeBlockLinks(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
if entering {
return ast.WalkContinue, nil
}
codeBlock := node.(*ast.FencedCodeBlock)

var content string
for i := 0; i < codeBlock.Lines().Len(); i++ {
line := codeBlock.Lines().At(i)
content += string(line.Value(source))
}
lexer := lexers.Get(string(codeBlock.Language(source)))
if lexer == nil {
w.WriteString("<pre class=\"codeblock\"><code>")
w.WriteString(content)
w.WriteString("</code></pre>")
return ast.WalkContinue, nil
}
lexer = chroma.Coalesce(lexer)

formatter := html.New(html.WithClasses(true), html.PreventSurroundingPre(true))

iterator, err := lexer.Tokenise(nil, content)
if err != nil {
return ast.WalkStop, err
}
buf := new(bytes.Buffer)
// random style
formatter.Format(buf, styles.GitHub, iterator)

html := buf.String()
for _, attribute := range node.Attributes() {
attributeName := string(attribute.Name)
if !strings.HasPrefix(attributeName, "link:") {
continue
}
target := strings.Replace(attributeName, "link:", "", 1)
dest := attribute.Value.(string)
html = strings.ReplaceAll(html, "$$"+target, fmt.Sprintf("<a href=\"%s\">%s</a>", dest, target))
}

w.WriteString("<pre class=\"codeblock\"><code>")
w.WriteString(html)
w.WriteString("</code></pre>")

return ast.WalkContinue, nil
}

type Data struct {
Markdown template.HTML
Title string
Expand Down

0 comments on commit fbfa51a

Please sign in to comment.