Attempting to Learn Go - Building Dev Log Part 04
Tags: go, atlg, continuingeducation
Published: 2019-02-02 20:00:24

---
title: Attempting to Learn Go - Building Dev Log Part 04
published: true
description: "This time around we clean up a bit and write an index.html!"
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

Look at that! We're back in action. First thing I want to start off and say thanks for the constructive comments I received on the last post. I try to take time to consider any comment - after all, I'm still learning so I (admittedly) am not doing everything correct. All I can say for sure is that the code compiled on my machine. 😜

***

## Taking a Step Back

After looking over the code, and the comments I realized I may have been being a bit too terse with my variable names. Go is typically know for have short variable names but, it seems like it's could potentially make code harder to follow. With that in mind, I've begun to make the variable names more explicit. There are still cases where I am using a short name, `b` for a `[]byte` for example. Hopefully, those cases will still be pretty clear and easy to follow since I tried to make sure they are declared near where they are used.

With that said let's take a look where we are.

```go
package main

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

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

const delimiter = "---"

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

type index struct {
  Pages []Page
}

type Page struct {
  FileName string
  Title    string
}
```

I've added the index template and so we can generate a simple index page for the site. We pass in our `index` struct and range through the `Pages` to get each `Page` which contains the filename and the page title.

```go
var indexTempl = `<!DOCTYPE html>
<html lang="en">
  <head>
    <title>shindakun's dev site</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="shindakun's dev site" />
  </head>
  <body>
    <div class="index">
    {{ range $key, $value := .Pages }}
      <a href="/{{ $value.FileName }}">{{ $value.Title }}</a>
    {{ end }}
    </div>
  </body>
  </html>
`

var postTempl = `<!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>
`
```

It was pointed out by @ladydascalie in the comments that we would be better over accepting `io.Reader` this will allow me to load the file and easily mock tests of this function. We've also renamed the function to be much more descriptive. I decided to go with `getContentsOf()` so in the code it reads as `getContentsOf(openedFile)`.

```go
func getContentsOf(r io.Reader) ([]byte, error) {
  b, err := ioutil.ReadAll(r)
  if err != nil {
    return nil, err
  }
  return b, nil
}

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

func splitData(fm []byte) ([][]byte, error) {
  b := bytes.Split(fm, []byte(delimiter))
  if len(b) < 3 || len(b[0]) != 0 {
    return nil, fmt.Errorf("Front matter is damaged")
  }
  return b, nil
}
```

I also took @krusenas's suggestion to actually put some handling in place for the `makePost()`. We aren't always going to be working with known-good files and we really should consider that we'll have "bad" YAML before long. There is probably a less verbose way to handle this but it works for now. However, this is not done! We will have at least one more post in this part of the series, we'll need to reject the file if there is no title. After all, if there is no title we have nothing to link to in the index.

```go
// makePost creates the post struct, returns that and the template HTML
func makePost(fm map[string]interface{}, contents []byte, s [][]byte) (*template.Template, *post) {
  post := &post{}

  // TODO: Reject post if we don't have a title
  titleIntf, ok := fm["title"]
  if ok {
    title, ok := titleIntf.(string)
    if ok {
      post.Title = title
    } else {
      post.Title = ""
    }
  } else {
    post.Title = ""
  }

  pubIntf, ok := fm["published"]
  if ok {
    published, ok := pubIntf.(bool)
    if ok {
      post.Published = published
    } else {
      post.Published = false
    }
  } else {
    post.Published = false
  }

  descIntf, ok := fm["description"]
  if ok {
    description, ok := descIntf.(string)
    if ok {
      post.Description = description
    } else {
      post.Description = ""
    }
  } else {
    post.Description = ""
  }

  tagsIntf, ok := fm["tags"]
  if ok {
    tags, ok := tagsIntf.(string)
    if ok {
      post.Tags = strings.Split(tags, ", ")
    } else {
      post.Tags = []string{}
    }
  } else {
    post.Tags = []string{}
  }

  covIntf, ok := fm["cover_image"]
  if ok {
    coverImage, ok := covIntf.(string)
    if ok {
      post.CoverImage = coverImage
    } else {
      post.CoverImage = ""
    }
  } else {
    post.CoverImage = ""
  }

  seriesIntf, ok := fm["series"]
  if ok {
    series, ok := seriesIntf.(string)
    if ok {
      post.Series = series
    } else {
      post.Series = ""
    }
  } else {
    post.Series = ""
  }

  pBody := contents[len(s[1])+(len(delimiter)*2):]

  out := blackfriday.Run(pBody)

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

  tm := template.Must(template.New("post").Parse(postTempl))
  return tm, post
}
```

Our `writeIndex()` function is pretty simple and we might extend this out a bit so we have just one write to file function. We have basically the same code in our main loop. For now, though a little extra code is OK by me.

```go
func writeIndex(idx index) {
  indexFile, err := os.Create("index.html")
  if err != nil {
    panic(err)
  }
  defer indexFile.Close()
  buffer := bufio.NewWriter(indexFile)
  tm := template.Must(template.New("index").Parse(indexTempl))
  err = tm.Execute(buffer, idx)
  if err != nil {
    panic(err)
  }
  buffer.Flush()
}

func main() {
  var idx index

  dir, err := ioutil.ReadDir(".")
  if err != nil {
    panic(err)
  }

  for _, file := range dir {
    if fileName := file.Name(); strings.HasSuffix(fileName, ".md") {

      openedFile, err := os.Open(fileName)
      if err != nil {
        panic(err)
      }

      contents, err := getContentsOf(openedFile)
      if err != nil {
        panic(err)
      }
      s, err := splitData(contents)
      if err != nil {
        panic(err)
      }

      fm, err := parseFrontMatter(s[1])
      if err != nil {
        msg := fmt.Sprintf("error: %v\ninput:\n%s", err, s[1])
        panic(msg)
      }

      template, post := makePost(fm, contents, s)

      trimmedName := strings.TrimSuffix(fileName, ".md")
      outputFile, err := os.Create(trimmedName + ".html")
      if err != nil {
        panic(err)
      }
      defer outputFile.Close()

      buffer := bufio.NewWriter(outputFile)

      err = template.Execute(buffer, post)
      if err != nil {
        panic(err)
      }
      buffer.Flush()

      indexLinks := Page{
        FileName: trimmedName + ".html",
        Title:    post.Title,
      }
      idx.Pages = append(idx.Pages, indexLinks)
    }
  }
  writeIndex(idx)
}
```

In the end, we didn't have too many changes this week just a bit of cleaning up and adding the code to write our index file. This makes out code pretty complete, not perfect since we're not able to specify input and output directories but still useable. I ran a bit short in time this week anyway so it works out, I'll try to make add our last bits to close out this part of the series over the next week. Wonder what we should tackle after that... Have any ideas?

***

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) |

***