Attempting to Learn Go - Building Dev Log Part 02
Tags: go, atlg, continuingeducation
Published: 2019-01-19 20:19:30

---
title: Attempting to Learn Go - Building Dev Log Part 02
published: true
description: "Welcome to part two of our journey to build a static site generator!"
tags: go, atlg, continuing-education
cover_image: https://dev.shindakun.net/img/attemptinggo_w.png
series: "Attempting to Learn Go"
---

# Building Out A Dev Blog

[When last we met](https://dev.to/shindakun/attempting-to-learn-go---building-dev-log-part-01-1c3m) we had just read in a markdown file and parsed its contents. Not too exciting but a necessary first step toward building a static site generator. This time around we're going to continue working toward that goal! First, we'll tweak our struct a bit, introduce a new function for some basic testing the loaded markdown, and then output some real live HTML. As usual, if you have not read the previous post I recommend it, we're going to just jump in here with the new code.

***

## From The Top

We'll get started with our expanded set of imports. We're adding in `html/template`, `BlackFriday`, and `BlueMonday`. If you've read the [previous post on sending an email](https://dev.to/shindakun/attempting-to-learn-go---rest-api-and-a-bit-on-templates-4kca) you should be familiar with `text/template`, `html/template` is basically the same thing with added functions for dealing with HTML.

```go
package main

import (
  "bytes"
  "fmt"
  "html/template"
  "io/ioutil"
  "os"
  "strings"

  "github.com/microcosm-cc/bluemonday"
  "github.com/russross/blackfriday"
  yaml "gopkg.in/yaml.v2"
)

const delim = "---"
```

## Structural Changes

We need to export the items inside our struct so the template can use that data that's fed into it. Note, that we're also setting up our `PostBody` to have a type of `template.HTML`. We'll get into that in a little bit.

```go
type post struct {
  Title       string
  Published   bool
  Description string
  Tags        []string
  CoverImage  string
  Series      string
  PostBody    template.HTML
}
```

## HTML Template

Since we're stepping through this slowly, this version is only going to use a single built-in template. This is far from final but it's enough to show what we want to accomplish in this post. We'll move to load templates from files within a few iterations of the code.

```go
var templ = `<!DOCTYPE html>
<html lang="en">
  <head>
    <title>{{.Title}}</title>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="HandheldFriendly" content="True">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="referrer" content="no-referrer-when-downgrade" />
    <meta name="description" content="{{.Description}}" />
  </head>
  <body>
    <div class="post">
      <h1>{{.Title}}</h1>
      {{.PostBody}}
    </div>
  </body>
</html>`
```

## If Is Nil Panic

The `loadFile()` function hasn't changed at all so far so we'll skip over that to `isNil()`. You may recall if you read the previous post that, as it was written, it was to easy to trigger a `panic` if the front matter wasn't set. `isNil()` is the start of a larger effort to ensure that our incoming data is set. We'll be extending it to return some proper errors and not just bomb and stop the execution. As it stands right now we're only using it down in `main()` but, not really since it's presently commented out. (*cough*)

```go
func loadFile(s string) (b []byte, err error) {
  f, err := ioutil.ReadFile(s)
  if err != nil {
    return nil, err
  }
  return f, nil
}

func isNil(i interface{}) bool {
  if i != nil {
    return false
  }
  return true
}

func main() {
  f, err := loadFile("test.md")
  if err != nil {
    panic(err)
  }

  b := bytes.Split(f, []byte(delim))
  if len(b) < 3 || len(b[0]) != 0 {
    panic(fmt.Errorf("Front matter is damaged"))
  }

  m := make(map[string]interface{})
  err = yaml.Unmarshal([]byte(b[1]), &m)
  if err != nil {
    msg := fmt.Sprintf("error: %v\ninput:\n%s", err, b[1])
    panic(msg)
  }

  p := &post{}
```

## Is Nil Not Used

Here you can see the beginnings of where we'll use our `isNil()` code. I'm not very happy with it but, I was short on time so it's still in the works. I think that's alright for now since we're still working with a "known-good" test file. I'll have to give this part a closer look soon.

```go
  // if isNil(m["title"]) {
  // in final we wouldn't actually panic just reject the file and continue on
  //   panic(err)
  // } else {
  p.Title = m["title"].(string)
  p.Published = m["published"].(bool)
  p.Description = m["description"].(string)

  // TODO: Strip space after comma prior to parse?
  tmp := m["tags"].(string)
  p.Tags = strings.Split(tmp, ", ")

  p.CoverImage = m["cover_image"].(string)
  p.Series = m["series"].(string)
```

## Post Body

Last time, I posed the question of how we could continue to use the file we already loaded to get the post body. Well, this is one way! We have the file data in `f` which is a `[]byte` so we can take the length of our YAML header, `b[1]`, and add it to the length of two times the front matter delimiter (`---`). We use that as the start and go until the end of `f` which gives us what we need, `f[x:]`. This code is probably too fragile for "production" but for now we are going to live dangerously, the split check and the fact that the YAML needs to parse correctly should at least give us some coverage for now - we'll need to revisit this in the future I think.

```go
  pBody := f[len(b[1])+(len(delim)*2):]
```

`pBody` in hand now we need to pass it into `blackfriday`. For now, we'll just have `blackfriday`'s default markdown parser do its thing and return us a `[]byte` which we turn around and pass into `bluemonday`. I'm doing this almost exactly as suggested in the [`blackfriday` readme](https://github.com/russross/blackfriday), making sure to use the policy to preserve fenced code blocks. [`bluemonday`](https://github.com/microcosm-cc/bluemonday) is used for HTML sanitization. Using this should help ensure anything that is unintentionally nefarious in our posts gets cleaned up. Finally, we put everything together and dump to standard out. So right now if we happen to be on the server we can run `go run main.go > /var/www/dev.shindakun.net/index.html` and have at least a one-page static website. Not super exciting but we're making steady progress.

```go
  out := blackfriday.Run(pBody)

  bm := bluemonday.UGCPolicy()
  bm.AllowAttrs("class").Matching(regexp.MustCompile("^language-[a-zA-Z0-9]+$")).OnElements("code")
  p.PostBody = template.HTML(bm.SanitizeBytes(out))

  t := template.Must(template.New("msg").Parse(templ))

  err = t.Execute(os.Stdout, p)
  if err != nil {
    panic(err)
  }
}
```

***

## Output

![Image of homepage](https://thepracticaldev.s3.amazonaws.com/i/5mq3bs93wno7j0j934ea.png)

That's right! The fruits of our labor are currently (as of the time of writing this) live over on [dev.shindakun.net](https://dev.shindakun.net).

***

## Next Time

We have a good bit of the basics out of the way but, we have a long way left to go. Next time around, I hope to have, loading and outputting of multiple files mostly implemented. That should put us in a good place to take a step back and get some more error handling in place. I should probably start thinking about adding tests too.

***

You can find the code for this and most of the other Attempting to Learn Go posts in the repo on GitHub.

{% github shindakun/atlg %}

***

| Enjoy this post? |
|:----:|
| [How about buying me a coffee?](https://ko-fi.com/shindakun) |

***