├── demo.md
├── io-reader.md
├── how-to-boil-eggs.md
├── go.mod
├── post.gohtml
├── go.sum
└── main.go
/demo.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "Demo"
3 | description = "This is a demo blog post"
4 | date = 2024-03-01
5 |
6 | [author]
7 | name = "Jon Calhoun"
8 | email = "jon@calhoun.io"
9 | +++
10 |
11 | This is a demo blog post!
12 |
--------------------------------------------------------------------------------
/io-reader.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "Go's io.Reader"
3 | description = "In this article we explore the io.Reader interface provided by Go's io package."
4 | date = 2024-03-06
5 |
6 | [author]
7 | name = "Jon Calhoun"
8 | email = "jon@calhoun.io"
9 | +++
10 |
11 | The `io.Reader` is defined as:
12 |
13 | ```go
14 | type Reader interface {
15 | Read(p []byte) (n int, err error)
16 | }
17 | ```
18 |
--------------------------------------------------------------------------------
/how-to-boil-eggs.md:
--------------------------------------------------------------------------------
1 | +++
2 | title = "How to Boil Eggs"
3 | description = "Learn how to quickly and easily boil 6 eggs!"
4 | date = 2024-03-03
5 |
6 | [author]
7 | name = "Jon Calhoun"
8 | email = "jon@calhoun.io"
9 | +++
10 |
11 | Ingredients: 6 eggs
12 |
13 | Step 1: Boil 4 cups of water.
14 | Step 2: Add the eggs to the water and boil for 10 minutes.
15 | Step 3: Rinse eggs under cold water.
16 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/joncalhoun/jonblog
2 |
3 | go 1.22.1
4 |
5 | require (
6 | github.com/BurntSushi/toml v0.3.1 // indirect
7 | github.com/adrg/frontmatter v0.2.0 // indirect
8 | github.com/alecthomas/chroma/v2 v2.2.0 // indirect
9 | github.com/dlclark/regexp2 v1.7.0 // indirect
10 | github.com/yuin/goldmark v1.7.0 // indirect
11 | github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect
12 | gopkg.in/yaml.v2 v2.3.0 // indirect
13 | )
14 |
--------------------------------------------------------------------------------
/post.gohtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{.Title}} | Jon's Blog
8 |
9 |
10 |
23 |
24 |
{{.Title}}
25 | {{with .Author}}
26 |
27 |
Author: {{.Name}}
28 |
29 | {{end}}
30 |
31 | {{.Content}}
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3 | github.com/adrg/frontmatter v0.2.0 h1:/DgnNe82o03riBd1S+ZDjd43wAmC6W35q67NHeLkPd4=
4 | github.com/adrg/frontmatter v0.2.0/go.mod h1:93rQCj3z3ZlwyxxpQioRKC1wDLto4aXHrbqIsnH9wmE=
5 | github.com/alecthomas/chroma/v2 v2.2.0 h1:Aten8jfQwUqEdadVFFjNyjx7HTexhKP0XuqBG67mRDY=
6 | github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
7 | github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10 | github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
11 | github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
12 | github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
13 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
14 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
15 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
16 | github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
17 | github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA=
18 | github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
19 | github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
20 | github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
21 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
22 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
23 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
24 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
25 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "html/template"
6 | "io"
7 | "log"
8 | "net/http"
9 | "os"
10 | "strings"
11 |
12 | "github.com/adrg/frontmatter"
13 | "github.com/yuin/goldmark"
14 | highlighting "github.com/yuin/goldmark-highlighting/v2"
15 | )
16 |
17 | func main() {
18 | mux := http.NewServeMux()
19 |
20 | mux.HandleFunc("GET /posts/{slug}", PostHandler(FileReader{}))
21 |
22 | err := http.ListenAndServe(":3030", mux)
23 | if err != nil {
24 | log.Fatal(err)
25 | }
26 | }
27 |
28 | type SlugReader interface {
29 | Read(slug string) (string, error)
30 | }
31 |
32 | type FileReader struct{}
33 |
34 | func (fsr FileReader) Read(slug string) (string, error) {
35 | f, err := os.Open(slug + ".md")
36 | if err != nil {
37 | return "", err
38 | }
39 | defer f.Close()
40 | b, err := io.ReadAll(f)
41 | if err != nil {
42 | return "", err
43 | }
44 | return string(b), nil
45 | }
46 |
47 | func PostHandler(sl SlugReader) http.HandlerFunc {
48 | return func(w http.ResponseWriter, r *http.Request) {
49 | var post Post
50 | post.Slug = r.PathValue("slug")
51 | postMarkdown, err := sl.Read(post.Slug)
52 | if err != nil {
53 | // TODO: Handle different errors in the future
54 | http.Error(w, "Post not found", http.StatusNotFound)
55 | return
56 | }
57 | rest, err := frontmatter.Parse(strings.NewReader(postMarkdown), &post)
58 | if err != nil {
59 | http.Error(w, "Error parsing frontmatter", http.StatusInternalServerError)
60 | return
61 | }
62 | mdRenderer := goldmark.New(
63 | goldmark.WithExtensions(
64 | highlighting.NewHighlighting(
65 | highlighting.WithStyle("dracula"),
66 | ),
67 | ),
68 | )
69 | var buf bytes.Buffer
70 | err = mdRenderer.Convert(rest, &buf)
71 | if err != nil {
72 | http.Error(w, "Error converting markdown", http.StatusInternalServerError)
73 | return
74 | }
75 | // TODO: Parse the template once, not every page load.
76 | tpl, err := template.ParseFiles("post.gohtml")
77 | if err != nil {
78 | http.Error(w, "Error parsing template", http.StatusInternalServerError)
79 | return
80 | }
81 | post.Content = template.HTML(buf.String())
82 | err = tpl.Execute(w, post)
83 | }
84 | }
85 |
86 | type Post struct {
87 | Title string `toml:"title"`
88 | Slug string `toml:"slug"`
89 | Content template.HTML
90 | Author Author `toml:"author"`
91 | }
92 |
93 | type Author struct {
94 | Name string `toml:"name"`
95 | Email string `toml:"email"`
96 | }
97 |
98 | // type PostData struct {
99 | // Title string
100 | // Content template.HTML
101 | // Author string
102 | // }
103 |
--------------------------------------------------------------------------------