├── 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 | --------------------------------------------------------------------------------