ekzyis/markdown.go
2023-10-08 23:17:19 +02:00

172 lines
3.9 KiB
Go

package main
import (
"bufio"
"bytes"
"os"
"path/filepath"
"strconv"
"strings"
"text/template"
"github.com/PuerkitoBio/goquery"
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/html"
)
var (
MarkdownToHtmlFlags = (html.CommonFlags ^ html.SmartypantsFractions) | html.HrefTargetBlank
)
type MarkdownPost struct {
// file system path
FsPath string
// markdown content
Content []byte
// args parsed from markdown
Title string
Date string
ReadingTime string
Sats int
Comments string
}
func NewMarkdownPost(path string) *MarkdownPost {
post := MarkdownPost{FsPath: path}
post.LoadContent()
return &post
}
func (post *MarkdownPost) LoadContent() {
f, err := os.OpenFile(post.FsPath, os.O_RDONLY, 0755)
if err != nil {
panic(err)
}
sc := bufio.NewScanner(f)
post.ParseArgs(sc)
var content []byte
for sc.Scan() {
read := sc.Bytes()
content = append(content, read...)
content = append(content, '\n')
}
err = sc.Err()
if err != nil {
panic(err)
}
post.Content = content
}
func (post *MarkdownPost) ParseArgs(sc *bufio.Scanner) {
for sc.Scan() {
line := sc.Text()
parts := strings.Split(line, ":")
if len(parts) < 2 {
break
}
parts[1] = strings.Trim(parts[1], " \n")
switch parts[0] {
case "Title":
post.Title = strings.Join(parts[1:], ":")
case "Date":
post.Date = parts[1]
case "ReadingTime":
post.ReadingTime = parts[1]
case "Sats":
sats, err := strconv.Atoi(parts[1])
if err != nil {
panic(err)
}
post.Sats = int(sats)
case "Comments":
post.Comments = strings.Join(parts[1:], ":")
}
}
err := sc.Err()
if err != nil {
panic(err)
}
}
func (post *MarkdownPost) InsertHeader(htmlContent *[]byte) {
header := []byte("" +
"<code class=\"bg-transparent\"><strong><pre class=\"bg-transparent text-center\">\n" +
" _ _ \n" +
"| |__ | | ___ __ _ \n" +
"| '_ \\| |/ _ \\ / _` |\n" +
"| |_) | | (_) | (_| |\n" +
"|_.__/|_|\\___/ \\__, |\n" +
" |___/ </pre></strong></code>\n" +
"<div><div class=\"font-mono mb-1 text-center\">\n" +
"<strong>{{- .Title }}</strong><br />\n" +
"<small>{{- .Date }} | {{ .ReadingTime }} | <a target=\"_blank\" href=\"{{ .Comments }}\">Comments</a> | {{ .Sats }} sats</small>\n" +
"</div>\n")
*htmlContent = append(header, *htmlContent...)
}
func (post *MarkdownPost) StyleHtml(htmlContent *[]byte) {
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(*htmlContent))
if err != nil {
panic(err)
}
doc.Find("img").Each(func(index int, element *goquery.Selection) {
element.AddClass("flex m-auto")
})
doc.Find("pre, code").Each(func(index int, element *goquery.Selection) {
element.AddClass("code")
})
doc.Find("code[class*=\"language-\"]").Each(func(index int, element *goquery.Selection) {
SyntaxHighlighting(element)
})
htmlS, err := doc.Html()
if err != nil {
panic(err)
}
*htmlContent = []byte(htmlS)
}
func GetPosts(srcDir string) *[]MarkdownPost {
paths, err := filepath.Glob(srcDir + "*.md")
if err != nil {
panic(err)
}
var posts []MarkdownPost
for _, path := range paths {
post := NewMarkdownPost(path)
posts = append(posts, *post)
}
return &posts
}
func (post *MarkdownPost) Render() {
opts := html.RendererOptions{Flags: MarkdownToHtmlFlags}
renderer := html.NewRenderer(opts)
html := markdown.ToHTML(post.Content, nil, renderer)
post.InsertHeader(&html)
post.StyleHtml(&html)
t, err := template.New("post").Parse(string(html))
if err != nil {
panic(err)
}
t.Execute(os.Stdout, *post)
}
func RenderBlogIndex(srcDir string) {
posts := GetPosts(srcDir)
srcPath := srcDir + "index.html"
t := template.New(filepath.Base(srcPath))
t = t.Funcs(template.FuncMap{
"ToHref": func(fsPath string) string {
return "/" + strings.ReplaceAll(fsPath, ".md", "")
},
})
t, err := t.ParseFiles(srcPath)
if err != nil {
panic(err)
}
err = t.Execute(os.Stdout, map[string][]MarkdownPost{"Posts": *posts})
if err != nil {
panic(err)
}
}