Home

blog @main - refs - log -
-
https://git.jolheiser.com/blog.git
My nonexistent blog
tree log patch
wip Signed-off-by: jolheiser <john.olheiser@gmail.com>
Signature
-----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEgqEQpE3xoo1QwJO/uFOtpdp7v3oFAmXRTB4ACgkQuFOtpdp7 v3pfXg//ftDf+cwFxMFmazovPkyArIxj1Yp7kZdZtW8YKrq3hNOf86v1l+7uMRt1 jf/cm8Oa51xj9sf6HX84soD2k81llOwJRK4R0PJcADyQ9KtHeXMgVONPVhjS+y5i IEmedx4u/mNaq5ORMGSdpieOguAfBhva6NuFnAvydC98mYJ5+xhkrh6G11VUyxW8 d/GVMSaeoc12E9H6pR/pC+IQOfk/aUHTNh3Lv7+52gZijX5iICEGxZZZh9JcAUT5 OlvK+/4aQ7Urxka2mq4gXTP28YjiNnn6dU0s2oyaSlGhlW6YFHVXkHW8uRt6fAze 0UF3zyAhfCJuIx7twa9F2CqgHZAeGRFW5YkiwpCpeoNEdoPZXKADam2IEKMU3PVL c76R32YDczmhZM14VvUtmh/1yWYlcNYkGZNNTv2QzcTcF1kyzmUrWMwtjpMu071m MKiFFpddbbi9G9G7hbdD6DDN2CgPKjc7iocTYqyCrFhHRRwFcTBWGEqqDZd673Ni 24Xnxzc+kK5KHipuQ9cyj9fpeeRNpkT/gaMk3xsDg5TaCUSlG0qk3VhHhlBqQxyv 9Dp6MejY46gP0zQAmOAmPsk8GUYeniqmedgpZ8UnB+c0dUJR0E6XykHr5DOzjREf 3NQuxRxFz6lXs5AAuXA9Px3eP6bFigwBxWW8ZAbGs1lA2ccMRTU= =fMbW -----END PGP SIGNATURE-----
jolheiser <john.olheiser@gmail.com>
1 year ago
12 changed files, 548 additions(+), 22 deletions(-)
.gitignore_example/articles/introduction.md_example/templates/article.tmpl_example/templates/base.tmpl_example/templates/index.tmplblog.goblog_test.gocmd/blog/main.gocmd/chromacss/main.gogo.modgo.sumgoldmark.go
I .gitignore
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..c6aa2a4851f3a5271fdf9b86ce78e6e8ce1cbcef
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+/blog*
+!blog.go
+!blog_test.go
+
+_example/out
I _example/articles/introduction.md
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
diff --git a/_example/articles/introduction.md b/_example/articles/introduction.md
new file mode 100644
index 0000000000000000000000000000000000000000..ac53dbc5a9a19fa49dab18849b89e6f1fa8b26ee
--- /dev/null
+++ b/_example/articles/introduction.md
@@ -0,0 +1,36 @@
+---
+title: "Introduction"
+summary: "An introduction of `blog`"
+time: 2024-02-17
+authors:
+  - name: "jolheiser"
+    email: "john+blog@jolheiser.com"
+---
+
+# Hello and welcome to blog!
+
+`blog` is a simple static blog generator!
+
+<small>Truly cutting edge, I know. Never seen one of these before, have you?</small>
+
+```go
+package main
+
+import "fmt"
+
+func main() {
+  fmt.Println("Hello, blog!")
+}
+````
+
+Things it can do:
+
+- [x] Render code
+- [ ] Fill the void
+
+## Why was blog created?
+
+~~Because I needed a specific thing that other generators couldn't give me. Blog is up to 1000 times faster than \<hot other blog generator\>!!!~~
+
+Because I wanted to, like many other projects in this space. Blog is designed to be simple (to me) with enough flexibility for someone else to use without
+cursing at me (too much).
I _example/templates/article.tmpl
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
diff --git a/_example/templates/article.tmpl b/_example/templates/article.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..63c40605857d22e08af8ee9b7ad86b377094dd97
--- /dev/null
+++ b/_example/templates/article.tmpl
@@ -0,0 +1,4 @@
+{{template "head" .article.Title}}
+<body>
+<main>{{.article.Content}}</main>
+</body>
I _example/templates/base.tmpl
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
diff --git a/_example/templates/base.tmpl b/_example/templates/base.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..ea524bba9a9adc390eb0fa0525bdcc34ee7cac80
--- /dev/null
+++ b/_example/templates/base.tmpl
@@ -0,0 +1,12 @@
+{{define "head"}}
+<!DOCTYPE html>
+<head>
+<title>{{.}}</title>
+<!-- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/sakura.css/css/sakura.css" type="text/css"> -->
+<link
+  rel="stylesheet"
+  href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css"
+/>
+<link rel="stylesheet" href="chroma.css"/>
+</head>
+{{end}}
I _example/templates/index.tmpl
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
diff --git a/_example/templates/index.tmpl b/_example/templates/index.tmpl
new file mode 100644
index 0000000000000000000000000000000000000000..8f02204de131292ae68007925ca62f4a782d2d87
--- /dev/null
+++ b/_example/templates/index.tmpl
@@ -0,0 +1,8 @@
+{{template "head" .author.Name}}
+<body>
+<main>
+{{range $article := .articles}}
+<a href="{{$article.Filename}}.html">{{$article.Title}}</a>
+{{end}}
+</main>
+</body>
M blog.goblog.go
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
diff --git a/blog.go b/blog.go
index 5f29fbbd4e2e070101295601d62d5c76e0a1b995..644353a354057efe605a8a87576faa2ecb9dc9c9 100644
--- a/blog.go
+++ b/blog.go
@@ -1,15 +1,22 @@
 package blog
 
 import (
+	"bytes"
 	"errors"
 	"fmt"
 	"html/template"
+	"io"
 	"io/fs"
 	"net/url"
 	"os"
+	"path/filepath"
+	"sort"
+	"strings"
 	"time"
 
 	"github.com/bmatcuk/doublestar/v4"
+	"github.com/pelletier/go-toml/v2"
+	"gopkg.in/yaml.v3"
 )
 
 // Blog is a collection of [Article]
@@ -17,21 +24,28 @@ type Blog struct {
 	indexTemplate   *template.Template
 	articleTemplate *template.Template
 	Articles        []Article
+	Author          Author
 }
 
 // Article is a blog post/article
 type Article struct {
-	Path     string
+	Filename string
+	Content  template.HTML
+	ArticleMeta
+}
+
+// ArticleMeta is the metadata of an [Article]
+type ArticleMeta struct {
 	Title    string
 	Subtitle string
 	Summary  string
-	Content  string
 	Time     time.Time
-	Authors  []Author
+	Author   Author
 	Tags     []string
+	Category string
 }
 
-// Author is an author of a blog post
+// Author is an author of a blog/post
 type Author struct {
 	Name  string
 	Job   string
@@ -42,34 +56,75 @@
 // Link is a link name and URL
 type Link struct {
 	Name string
-	URL  *url.URL
+	URL  LinkURL
+}
+
+// LinkURL is a URL
+type LinkURL url.URL
+
+func (l *LinkURL) UnmarshalText(data []byte) error {
+	u, err := url.Parse(string(data))
+	if err != nil {
+		return err
+	}
+	*l = LinkURL(*u)
+	return nil
 }
 
-func NewBlog(dir string) (*Blog, error) {
-	tmpl, err := parseTemplates(os.DirFS(dir))
+// NewBlog constructs a new blog from articles in articleDir and templates in templateDir
+func NewBlog(articleDir, templateDir string, author Author) (*Blog, error) {
+	tmpl, err := parseTemplates(os.DirFS(templateDir))
 	if err != nil {
-		return nil, fmt.Errorf("could not parse templates in %q: %w", dir, err)
+		return nil, fmt.Errorf("could not parse templates in %q: %w", templateDir, err)
+	}
+	indexTmpl := tmpl.Lookup("index.tmpl")
+	if indexTmpl == nil {
+		indexTmpl = tmpl.Lookup("index")
+		if indexTmpl == nil {
+			return nil, errors.New("`index` template is required but was not found")
+		}
 	}
-	index := tmpl.Lookup("index")
-	if index == nil {
-		return nil, errors.New("`index` template is required but was not found")
+	articleTmpl := tmpl.Lookup("article.tmpl")
+	if articleTmpl == nil {
+		articleTmpl = tmpl.Lookup("article")
+		if articleTmpl == nil {
+			return nil, errors.New("`article` template is required but was not found")
+		}
 	}
-	article := tmpl.Lookup("article")
-	if article == nil {
-		return nil, errors.New("`article` template is required but was not found")
+	articles, err := parseArticles(os.DirFS(articleDir))
+	if err != nil {
+		return nil, fmt.Errorf("could not parse articles in %q: %w", articleDir, err)
 	}
 	return &Blog{
-		indexTemplate:   index,
-		articleTemplate: article,
+		indexTemplate:   indexTmpl,
+		articleTemplate: articleTmpl,
+		Articles:        articles,
+		Author:          author,
 	}, nil
 }
 
+// Index renders the blog index to w
+func (b *Blog) Index(w io.Writer) error {
+	return b.indexTemplate.Execute(w, map[string]any{
+		"articles": b.Articles,
+		"author":   b.Author,
+	})
+}
+
+// Article renders an article to w
+func (b *Blog) Article(w io.Writer, a Article) error {
+	return b.articleTemplate.Execute(w, map[string]any{
+		"article": a,
+		"author":  b.Author,
+	})
+}
+
 func parseTemplates(fs fs.FS) (*template.Template, error) {
 	matches, err := doublestar.Glob(fs, "**/*.{tmpl,gohtml}")
 	if err != nil {
 		return nil, fmt.Errorf("could not glob templates: %w", err)
 	}
-	tmpl, err := template.New("").ParseFiles(matches...)
+	tmpl, err := template.New("").ParseFS(fs, matches...)
 	if err != nil {
 		return nil, fmt.Errorf("could not parse templates: %w", err)
 	}
@@ -81,6 +136,103 @@ 	matches, err := doublestar.Glob(fs, "**/*.md")
 	if err != nil {
 		return nil, fmt.Errorf("could not glob articles: %w", err)
 	}
+	articles := make([]Article, 0, len(matches))
 	for _, match := range matches {
+		if err := func() error {
+			fi, err := fs.Open(match)
+			if err != nil {
+				return err
+			}
+			defer fi.Close()
+
+			content, err := io.ReadAll(fi)
+			if err != nil {
+				return err
+			}
+
+			article, err := parseArticle(string(content))
+			if err != nil {
+				return err
+			}
+			article.Filename = strings.TrimSuffix(filepath.Base(match), filepath.Ext(match))
+			articles = append(articles, article)
+			return nil
+		}(); err != nil {
+			return nil, err
+		}
 	}
+	sort.SliceStable(articles, func(i, j int) bool {
+		return articles[i].Time.After(articles[j].Time)
+	})
+	return articles, nil
+}
+
+func parseArticle(content string) (Article, error) {
+	lines := strings.Split(content, "\n")
+
+	start, end := -1, -1
+	var isSep func(string) bool
+	var decoder func([]byte, any) error
+
+	for idx, line := range lines {
+		if strings.TrimSpace(line) == "" {
+			continue
+		}
+		if isSep != nil && isSep(line) {
+			end = idx
+			break
+		}
+
+		if isTOMLSeparator(line) {
+			start = idx
+			isSep = isTOMLSeparator
+			decoder = toml.Unmarshal
+			continue
+		}
+
+		if isYAMLSeparator(line) {
+			start = idx
+			isSep = isYAMLSeparator
+			decoder = yaml.Unmarshal
+			continue
+		}
+	}
+
+	var meta ArticleMeta
+	body := content
+	if start != -1 && end != -1 {
+		body = strings.Join(lines[end+1:], "\n")
+		if err := decoder([]byte(strings.Join(lines[start+1:end], "\n")), &meta); err != nil {
+			return Article{}, fmt.Errorf("could not parse frontmatter: %w", err)
+		}
+	}
+
+	var buf bytes.Buffer
+	err := Markdown.Convert([]byte(body), &buf)
+	if err != nil {
+		return Article{}, fmt.Errorf("could not convert article: %w", err)
+	}
+
+	return Article{
+		Content:     template.HTML(buf.String()),
+		ArticleMeta: meta,
+	}, nil
+}
+
+func isTOMLSeparator(line string) bool {
+	for _, char := range line {
+		if char != '+' {
+			return false
+		}
+	}
+	return true
+}
+
+func isYAMLSeparator(line string) bool {
+	for _, char := range line {
+		if char != '-' {
+			return false
+		}
+	}
+	return true
 }
I blog_test.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
diff --git a/blog_test.go b/blog_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..821a659ae04f26a6c4bb1157f8c4835e58de07b2
--- /dev/null
+++ b/blog_test.go
@@ -0,0 +1,72 @@
+package blog
+
+import (
+	"net/url"
+	"testing"
+	"time"
+
+	"github.com/alecthomas/assert"
+)
+
+func TestParseArticle(t *testing.T) {
+	var (
+		raw = `+++
+title = "Honk"
+subtitle = "Bonk"
+summary = """This
+is
+a
+summary"""
+time = 2024-02-16
+tags = ["bocchi", "rocks"]
+		
+[author]
+name = "Bocchi"
+job = "Guitarist"
+email = "bocchi@rock.s"
+
+[[author.links]]
+name = "website"
+url = "https://example.com/bocchi"
++++
+
+# Hello world
+
+Beep boop`
+		meta = ArticleMeta{
+			Title:    "Honk",
+			Subtitle: "Bonk",
+			Summary: `This
+is
+a
+summary`,
+			Time: func() time.Time {
+				t, _ := time.ParseInLocation("2006-01-02", "2024-02-16", time.Local)
+				return t
+			}(),
+			Author: Author{
+				Name:  "Bocchi",
+				Job:   "Guitarist",
+				Email: "bocchi@rock.s",
+				Links: []Link{
+					{
+						Name: "website",
+						URL: func() LinkURL {
+							u, _ := url.Parse("https://example.com/bocchi")
+							return LinkURL(*u)
+						}(),
+					},
+				},
+			},
+			Tags: []string{"bocchi", "rocks"},
+		}
+		body = `<h1 id="hello-world">Hello world</h1>
+<p>Beep boop</p>
+`
+	)
+
+	article, err := parseArticle(raw)
+	assert.NoError(t, err)
+	assert.Equal(t, meta, article.ArticleMeta)
+	assert.Equal(t, body, article.Content)
+}
I cmd/blog/main.go
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
diff --git a/cmd/blog/main.go b/cmd/blog/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..d5d835e2eab4153e10fc157dd07aec9cbb7507d4
--- /dev/null
+++ b/cmd/blog/main.go
@@ -0,0 +1,116 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/pelletier/go-toml/v2"
+	"go.jolheiser.com/blog"
+	"gopkg.in/yaml.v3"
+)
+
+type args struct {
+	ArticleDir  string      `yaml:"article-dir" toml:"article-dir"`
+	TemplateDir string      `yaml:"template-dir" toml:"template-dir"`
+	OutDir      string      `yaml:"out-dir" toml:"out-dir"`
+	Author      blog.Author `yaml:"author" toml:"author"`
+}
+
+func main() {
+	if err := maine(); err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+}
+
+func maine() error {
+	var args args
+
+	fs := flag.NewFlagSet("blog", flag.ExitOnError)
+
+	configFlag := fs.String("config", "", "Configuration, TOML or YAML")
+	fs.StringVar(configFlag, "c", *configFlag, "--config")
+	articlesFlag := fs.String("articles", "", "Path to articles")
+	fs.StringVar(articlesFlag, "a", *articlesFlag, "--articles")
+	templatesFlag := fs.String("templates", "", "Path to templates")
+	fs.StringVar(templatesFlag, "t", *templatesFlag, "--templates")
+	outFlag := fs.String("out", "", "Path to output")
+	fs.StringVar(outFlag, "o", *outFlag, "--out")
+
+	if err := fs.Parse(os.Args[1:]); err != nil {
+		return err
+	}
+
+	if *configFlag != "" {
+		data, err := os.ReadFile(*configFlag)
+		if err != nil {
+			return err
+		}
+		switch ext := filepath.Ext(*configFlag); ext {
+		case ".yaml", ".yml":
+			if err := yaml.Unmarshal(data, &args); err != nil {
+				return err
+			}
+		case ".toml":
+			if err := toml.Unmarshal(data, &args); err != nil {
+				return err
+			}
+		default:
+			return fmt.Errorf("could not determine config type %q", ext)
+		}
+	}
+
+	if *articlesFlag != "" {
+		args.ArticleDir = *articlesFlag
+	}
+	if args.ArticleDir == "" {
+		args.ArticleDir = "articles"
+	}
+	if *templatesFlag != "" {
+		args.TemplateDir = *templatesFlag
+	}
+	if args.TemplateDir == "" {
+		args.TemplateDir = "templates"
+	}
+	if *outFlag != "" {
+		args.OutDir = *outFlag
+	}
+	if args.OutDir == "" {
+		args.OutDir = "out"
+	}
+
+	if err := os.MkdirAll(args.OutDir, os.ModePerm); err != nil {
+		return err
+	}
+
+	blog, err := blog.NewBlog(args.ArticleDir, args.TemplateDir, args.Author)
+	if err != nil {
+		return err
+	}
+
+	indexFile, err := os.Create(filepath.Join(args.OutDir, "index.html"))
+	if err != nil {
+		return err
+	}
+	defer indexFile.Close()
+	if err := blog.Index(indexFile); err != nil {
+		return err
+	}
+
+	for _, article := range blog.Articles {
+		if err := func() error {
+			articleFile, err := os.Create(filepath.Join(args.OutDir, article.Filename+".html"))
+			if err != nil {
+				return err
+			}
+			defer articleFile.Close()
+			return blog.Article(articleFile, article)
+		}(); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
I cmd/chromacss/main.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
diff --git a/cmd/chromacss/main.go b/cmd/chromacss/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..edf8cd5022a0e2574837c673ee0f869f4389a39f
--- /dev/null
+++ b/cmd/chromacss/main.go
@@ -0,0 +1,71 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"os"
+
+	"github.com/alecthomas/chroma/v2/formatters/html"
+	"github.com/alecthomas/chroma/v2/styles"
+)
+
+func main() {
+	if err := maine(); err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+}
+
+func maine() error {
+	fs := flag.NewFlagSet("chromacss", flag.ExitOnError)
+	lightFlag := fs.String("light", "catppuccin-latte", "Light theme")
+	fs.StringVar(lightFlag, "l", *lightFlag, "--light")
+	darkFlag := fs.String("dark", "catppuccin-mocha", "Dark theme")
+	fs.StringVar(darkFlag, "d", *darkFlag, "--dark")
+	outFlag := fs.String("out", "", "Output (default: stdout)")
+	fs.StringVar(outFlag, "o", *outFlag, "--out")
+	if err := fs.Parse(os.Args[1:]); err != nil {
+		return err
+	}
+
+	out := os.Stdout
+	if *outFlag != "" {
+		fi, err := os.Create(*outFlag)
+		if err != nil {
+			return err
+		}
+		defer fi.Close()
+		out = fi
+	}
+
+	formatter := html.New(html.WithClasses(true), html.WithAllClasses(true))
+
+	lightStyle := *lightFlag
+	darkStyle := *darkFlag
+
+	if lightStyle == "" {
+		lightStyle = "catppuccin-latte"
+		if darkStyle != "" {
+			lightStyle = darkStyle
+			darkStyle = ""
+		}
+	}
+
+	styles.Fallback = styles.Get("catppuccin-latte")
+	style := styles.Get(lightStyle)
+	if err := formatter.WriteCSS(out, style); err != nil {
+		return err
+	}
+
+	if darkStyle != "" {
+		out.WriteString("@media (prefers-color-scheme: dark) {")
+		styles.Fallback = styles.Get("catpuccin-mocha")
+		style = styles.Get(darkStyle)
+		if err := formatter.WriteCSS(out, style); err != nil {
+			return err
+		}
+		out.WriteString("}")
+	}
+
+	return nil
+}
M go.modgo.mod
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
diff --git a/go.mod b/go.mod
index ebf428e62bc7377fd661ad412ab8c6c312f3c820..8c1aa017c7924a7b8ed3b4ceab8cde26ece9de51 100644
--- a/go.mod
+++ b/go.mod
@@ -3,13 +3,24 @@
 go 1.21.6
 
 require (
+	github.com/BurntSushi/toml v1.3.2 // indirect
+	github.com/alecthomas/assert v1.0.0 // indirect
 	github.com/alecthomas/chroma/v2 v2.12.0 // indirect
+	github.com/alecthomas/colour v0.1.0 // indirect
+	github.com/alecthomas/repr v0.2.0 // indirect
 	github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
 	github.com/dlclark/regexp2 v1.10.0 // indirect
+	github.com/gorilla/feeds v1.1.2 // indirect
+	github.com/jolheiser/goldmark-meta v0.0.2 // indirect
+	github.com/mattn/go-isatty v0.0.14 // indirect
+	github.com/pelletier/go-toml/v2 v2.1.1 // indirect
 	github.com/peterbourgon/ff/v3 v3.4.0 // indirect
+	github.com/sergi/go-diff v1.2.0 // indirect
 	github.com/yuin/goldmark v1.7.0 // indirect
 	github.com/yuin/goldmark-emoji v1.0.2 // indirect
 	github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect
 	github.com/yuin/goldmark-meta v1.1.0 // indirect
+	golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
M go.sumgo.sum
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
diff --git a/go.sum b/go.sum
index caa7d4f6c0a660af8270928e7bdf133b7c732019..62a81853e4095cb8c64512a1f9d8ff9e25a8fc83 100644
--- a/go.sum
+++ b/go.sum
@@ -1,8 +1,16 @@
+github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
+github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/alecthomas/assert v1.0.0 h1:3XmGh/PSuLzDbK3W2gUbRXwgW5lqPkuqvRgeQ30FI5o=
+github.com/alecthomas/assert v1.0.0/go.mod h1:va/d2JC+M7F6s+80kl/R3G7FUiW6JzUO+hPhLyJ36ZY=
 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/chroma/v2 v2.12.0 h1:Wh8qLEgMMsN7mgyG8/qIpegky2Hvzr4By6gEF7cmWgw=
 github.com/alecthomas/chroma/v2 v2.12.0/go.mod h1:4TQu7gdfuPjSh76j78ietmqh9LiurGF0EpseFXdKMBw=
+github.com/alecthomas/colour v0.1.0 h1:nOE9rJm6dsZ66RGWYSFrXw461ZIt9A6+nHgL7FRrDUk=
+github.com/alecthomas/colour v0.1.0/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
 github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
+github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
+github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
 github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
 github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -12,11 +20,31 @@ github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
 github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
 github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
 github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
+github.com/gorilla/feeds v1.1.2 h1:pxzZ5PD3RJdhFH2FsJJ4x6PqMqbgFk1+Vez4XWBW8Iw=
+github.com/gorilla/feeds v1.1.2/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
+github.com/jolheiser/goldmark-meta v0.0.2 h1:3qnvGnp1n7Cdu1L9LpuZrM6dfmbMAS22+hNJnxGPn6s=
+github.com/jolheiser/goldmark-meta v0.0.2/go.mod h1:x5vZW1+kBEhR4+AKHwnNsD+nQaYdgmXoiFgG3fc6ZFc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
+github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
+github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
 github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc=
 github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
+github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/yuin/goldmark v1.3.7 h1:NSaHgaeJFCtWXCBkBKXw0rhgMuJ0VoE9FB5mWldcrQ4=
 github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/yuin/goldmark v1.4.15 h1:CFa84T0goNn/UIXYS+dmjjVxMyTAvpOmzld40N/nfK0=
@@ -29,7 +57,14 @@ github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
 github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
 github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
M goldmark.gogoldmark.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
diff --git a/goldmark.go b/goldmark.go
index 99f519f9fdc3e2bc2d605a469bc28c8d51a4eba7..404e8a5ff1189ddd0c7569bb22b4a44217dca46b 100644
--- a/goldmark.go
+++ b/goldmark.go
@@ -1,13 +1,14 @@
 package blog
 
 import (
-	"github.com/alecthomas/chroma/v2/formatters/html"
+	chromahtml "github.com/alecthomas/chroma/v2/formatters/html"
 	"github.com/yuin/goldmark"
 	emoji "github.com/yuin/goldmark-emoji"
 	highlighting "github.com/yuin/goldmark-highlighting/v2"
 	meta "github.com/yuin/goldmark-meta"
 	"github.com/yuin/goldmark/extension"
 	"github.com/yuin/goldmark/parser"
+	"github.com/yuin/goldmark/renderer/html"
 )
 
 // Markdown is the default markdown converter
@@ -16,16 +17,19 @@ var Markdown = goldmark.New(
 	goldmark.WithParserOptions(
 		parser.WithAutoHeadingID(),
 	),
+	goldmark.WithRendererOptions(
+		html.WithUnsafe(),
+	),
 	goldmark.WithExtensions(
 		extension.GFM,
 		meta.Meta,
 		emoji.Emoji,
 		highlighting.NewHighlighting(
 			highlighting.WithFormatOptions(
-				html.WithClasses(true),
-				html.WithLineNumbers(true),
-				html.WithLinkableLineNumbers(true, "code-"),
-				html.LineNumbersInTable(true),
+				chromahtml.WithClasses(true),
+				chromahtml.WithAllClasses(true),
+				chromahtml.WithLineNumbers(true),
+				chromahtml.WithLinkableLineNumbers(true, "code-"),
 			),
 		),
 	),