Greg's Thoughts


Making The Blog Part 3: Implementing Tags

I think that I've implemented tags. It's pretty neat.

The actual implementation had two major nits. One is organizational. I didn't want to have arbitrary magic metadata floating around, so I did what any good go template writer would do, and tossed all the important stuff into a struct. Two structs, actually, though renderingPage only gets filled in juuuuuuuust before the page is put into the template. It has PagesByTag, which is all pages in the parse ordered by Created date.

type parsedPage struct {
	Template   string
	Parameters map[string]interface{}
	Tags       []string
	Content    *bytes.Buffer
	dir        os.DirEntry
	Path       string
	Title      string
	Created    time.Time
}

type renderingPage struct {
	*parsedPage
	PagesByTag map[string][]*parsedPage
}

The exported vars, Template, Parameters, Tags and everything, are available to the template. It's possible that Path will be restricted at some point in the future.

The other hairy part is that the the tags in the blog are not necessarily parsed by the goldmark Metadata parser as a []string, as I'd want, nor is the user (me) necessarily providing the tags as such. Maybe I provide only one tag, or accidentally nest too deep. In this case, I wanted to handle it safely. So here's the hairy code. I may in fact report errors, such as when the tags aren't strings, but for now, this is what I've got and I mostly just need to use it and see what feels right.

func readTags(metadata map[string]interface{}) ([]string, error) {
	ts, ok := metadata["tags"]
	if !ok {
		return nil, nil
	}

	tst, ok := ts.(string)
	if ok {
		ret := make([]string, 1)
		ret[0] = tst
		return ret, nil
	}

	tsa, ok := ts.([]string)
	if ok {
		return tsa, nil
	}
	tso, ok := ts.([]interface{})
	if ok {
		ret := make([]string, len(tso))

		for _, x := range tso {
			xs, ok := x.(string)
			if ok {
				ret = append(ret, xs)
			}
		}
		return ret, nil
	}

	return nil, nil
}

Probably doesn't pass readability. err is always nil, and can be deleted. Or, you know, return an error.

In any case, that's where I'm at. We're FeatureComplete baby! Now time for some templates!