BEER2RST - my first attempt with golang

Posted by Marcus Folkesson on Friday, July 13, 2018

BEER2RST - my first attempt with golang

I'm using Beersmith [1] a (non-free...) software to create my beer recipes. It's written in Java and runs well on Linux. One of the biggest benefits with using Beersmith is that my brewing house [2] is taking exported recipes as input and setup mash schemes automatically - really comfortable.

I brew beer several times a month and always takes notes of each brewing, both methods and results. The goal is to get reproducible results each time or to make small improvements. Instead of having all these notes in separate locations, it would be nice if I instead collected all of my brewing as blog posts.

With that said, this blog will probably evolve to contain non-technical posts as well in the near future.

In these posts, I also want to publish my recipes somehow. Beersmith is able to export recipes in a non complicated XML format and is quite straight forward to parse. All posts that I'm writing is in reStructuredText [3] format, so I need to create a tool that read the XML and export the recipes in reStructuredText format.

First glance at Golang

/media/golang.png

To be honest, I'm not really a fan of high-level programming languages as I can't take them seriously. But I guess it's time to learn something new. What I have tested with Go so far is rather impressive. For example, I cannot imagine a simpler XML parsing (I'm used to libxml2).

I also like the gofmt [4] tool to make the code properly formatted. Every language should have such a tool. It's also easy so cross compile the application to different architectures by specify $GOOS and $GOARCH. I have only tested with ARM and it just works.

Imports

Most languages has a way to tell what functionality it should import and be usable to your file. Go is using import and has the following syntax

import (
    "encoding/xml"
    "flag"
    "fmt"
    "io/ioutil"
    "os"
    "strings"
)

What makes it really interesting is when you do this

import (
    "github.com/marcusfolkesson/tablewriter"
)

I.e. point to a repository. You only need to download the repository to your $GOPATH location and then it's useable

go get github.com/marcusfolkesson/tablewriter
Notes:
I need to create tables for print the recipes properly. I found tablewriter [5] that is printing ASCII-tables. Unfortunately, it does not create tables in reStructuredText format so I had to fork the project and implement support for that. Hopefully the changes will make it back to the original project. There is a pending pull request for that.

Unmarshal XML

I really liked how easy it was to unmarshal XML. Consider the following snip from the exported recipe

<HOP>
 <NAME>Northern Brewer</NAME>
 <VERSION>1</VERSION>
 <ORIGIN>Germany</ORIGIN>
 <ALPHA>8.5000000</ALPHA>
 <AMOUNT>0.0038459</AMOUNT>
 <USE>Boil</USE>
 <TIME>60.0000000</TIME>
 <NOTES>Also called Hallertauer Northern Brewers
Used for: Bittering and finishing both ales and lagers of all kinds
Aroma: Fine, dry, clean bittering hop.  Unique flavor.
Substitutes: Hallertauer Mittelfrueh, Hallertauer
Examples: Anchor Steam, Old Peculiar, </NOTES>
 <TYPE>Both</TYPE>
 <FORM>Pellet</FORM>
 <BETA>4.0000000</BETA>
 <HSI>35.0000000</HSI>
 <DISPLAY_AMOUNT>3.85 g</DISPLAY_AMOUNT>
 <INVENTORY>0.00 g</INVENTORY>
 <DISPLAY_TIME>60.0 min</DISPLAY_TIME>
</HOP>

The first step is to create a structure that should hold the values

type Hop struct {
    Name   string  `xml:"NAME"`
    Origin string  `xml:"ORIGIN"`
    Alpha  float64 `xml:"ALPHA"`
    Amount float64 `xml:"AMOUNT"`
    Use    string  `xml:"USE"`
    Time   float64 `xml:"TIME"`
    Notes  string  `xml:"NOTES"`
}

There is no need to create variables for all tags, just the ones you are interesting in.

Later on, read the file and unmarshal the XML

content, err := ioutil.ReadFile("beer.xml")
if err != nil {
        panic(err)
}

err = xml.Unmarshal(content, &hops)
if err != nil {
        panic(err)
}

The structure will now be populated with values from the XML file. Magical, isn't it?

Conclusion

I have only used tested Golang for a working day approximately, and I like it. I used to use Python for all kind of fast prototyping, but I think I will consider Golang the next time.

What I really like in comparison with Python are:

  • The fact that it compiles to a single binary is really nice, especially when you cross compile to a different architecture.
  • No need for interpreter
  • Static types! Dynamic type languages makes my brain hurt

The result can be found on my GitHub [6] account.