Reading OS X file tags I'm investigating Go for use in our internal network / office project management system, and need a way to check and edit the color tags that you can set in Finder on files or folders (extended attributes, xattrs).  This is my first Go code.  Is this a reasonable way to go about it, and is the interface something that makes sense?

Using a const() "fake enum" type thing, with a type TagColor like this - is this going to annoy users who just want a straight int, or is it actually helpful for keeping things typed?  Likewise putting a String() method for the type - I find it pretty helpful, but is this just making things needlessly bloated?

I want it to be a friendly interface to work with, and not be over-engineered or over-complex.

This is currently just this one file:

/*
 * OSX Tags
 * (C) 2014 Daniel Fairhead
 * MIT Licence
**/

package osxtags

import "fmt"

import "github.com/AlexSi/xattr"
import "howett.net/plist"

type TagColor int

const (
    None   TagColor = 0
    Gray   TagColor = 1
    Green  TagColor = 2
    Purple TagColor = 3
    Blue   TagColor = 4
    Yellow TagColor = 5
    Red    TagColor = 6
    Orange TagColor = 7
)

func (t TagColor) String() string {
    switch t {
    case None:
        return "None"
    case Gray:
        return "Gray"
    case Green:
        return "Green"
    case Purple:
        return "Purple"
    case Blue:
        return "Blue"
    case Yellow:
        return "Yellow"
    case Red:
        return "Red"
    case Orange:
        return "Orange"
    }
    return "unknown"
}

type ColorTag struct {
    Color TagColor
    Name  string
}

func GetColors(filename string) ([]ColorTag, error) {

    // pull out the raw metadata:

    colormeta, err := xattr.Get(filename, "com.apple.metadata:_kMDItemUserTags")

    if err != nil {
        return nil, err
    }

    // decode it from a binary plist:

    colList := []string{}

    _, err = plist.Unmarshal(colormeta, &amp;colList)

    if err != nil {
        return nil, err
    }

    // split the stupid "\n" items into (color, name) pairs.

    toReturn := make([]ColorTag, len(colList), len(colList))

    for i, col := range colList {
        fmt.Sscanf(col, "%s\n%d", &amp;toReturn[i].Name, &amp;toReturn[i].Color)
    }

    return toReturn, nil

}
Using a custom type (with underlying type int) and using const to define the predefined colors is the perfect way to go.

If a user would want to use int, it is still possible by using a simple type conversion:

var myIntColor = 1 // Gray
var mycolor TagColor = TagColor(myIntColor)


Although by not using the predefined constants, you increase the possibility of errors (as the compiler will not be able to provide compile-time checks).

Note that your constant declaration can be a lot more compact and idiomatic by using the iota predeclared identifier which is auto-incremented, so you can use = iota to give value to all your colors. And also quoting from the spec: Constant declarations:


  Within a parenthesized const declaration list the expression list may be omitted from any but the first declaration. Such an empty list is equivalent to the textual substitution of the first preceding non-empty expression list and its type if any. Omitting the list of expressions is therefore equivalent to repeating the previous list.


This means you only have to write the initialization value for the first constant, and if you omit the rest, the same will be used for the rest (but iota will be auto-incremented of course). So your constant declaration can be this:

const (
    None TagColor = iota
    Gray
    Green
    Purple
    Blue
    Yellow
    Red
    Orange
)


Specifying a String() method for your type is also recommended, as many of the standard library functions check and use that if provided, for example the fmt package when printing values:

col := Orange
fmt.Println(col) // Will print "Orange"


The fmt package checks if the type of the value you passed has a String() method, and if it does, it will be called and its result will be printed.

Also note that you can "automatically" generate the String() method for your constants with go generate. There is a stringer tool which can be used with go generate and it generates a .go source file doing more or less what you did, but with a more efficient code. You can read more about go generate in the blog post Generating code.