toml

package module
v0.3.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 15, 2018 License: MIT Imports: 15 Imported by: 0

README

TOML parser and encoder for Go with reflection

TOML stands for Tom's Obvious, Minimal Language. This Go package provides a reflection interface similar to Go's standard library json and xml packages. This package also supports the encoding.TextUnmarshaler and encoding.TextMarshaler interfaces so that you can define custom data representations. (There is an example of this below.)

Spec: https://github.com/toml-lang/toml

Compatible with TOML version v0.4.0

Documentation: https://godoc.org/github.com/BurntSushi/toml

Installation:

go get github.com/BurntSushi/toml

Try the toml validator:

go get github.com/BurntSushi/toml/cmd/tomlv
tomlv some-toml-file.toml

Build Status GoDoc

Testing

This package passes all tests in toml-test for both the decoder and the encoder.

Examples

This package works similarly to how the Go standard library handles XML and JSON. Namely, data is loaded into Go values via reflection.

For the simplest example, consider some TOML file as just a list of keys and values:

Age = 25
Cats = [ "Cauchy", "Plato" ]
Pi = 3.14
Perfection = [ 6, 28, 496, 8128 ]
DOB = 1987-07-05T05:45:00Z

Which could be defined in Go as:

type Config struct {
  Age int
  Cats []string
  Pi float64
  Perfection []int
  DOB time.Time // requires `import time`
}

And then decoded with:

var conf Config
if _, err := toml.Decode(tomlData, &conf); err != nil {
  // handle error
}

You can also use struct tags if your struct field name doesn't map to a TOML key value directly:

some_key_NAME = "wat"
type TOML struct {
  ObscureKey string `toml:"some_key_NAME"`
}

Using the encoding.TextUnmarshaler interface

Here's an example that automatically parses duration strings into time.Duration values:

[[song]]
name = "Thunder Road"
duration = "4m49s"

[[song]]
name = "Stairway to Heaven"
duration = "8m03s"

Which can be decoded with:

type song struct {
  Name     string
  Duration duration
}
type songs struct {
  Song []song
}
var favorites songs
if _, err := toml.Decode(blob, &favorites); err != nil {
  log.Fatal(err)
}

for _, s := range favorites.Song {
  fmt.Printf("%s (%s)\n", s.Name, s.Duration)
}

And you'll also need a duration type that satisfies the encoding.TextUnmarshaler interface:

type duration struct {
	time.Duration
}

func (d *duration) UnmarshalText(text []byte) error {
	var err error
	d.Duration, err = time.ParseDuration(string(text))
	return err
}

More complex usage

Here's an example of how to load the example from the official spec page:

# This is a TOML document. Boom.

title = "TOML Example"

[owner]
name = "Tom Preston-Werner"
organization = "GitHub"
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
dob = 1979-05-27T07:32:00Z # First class dates? Why not?

[database]
server = "192.168.1.1"
ports = [ 8001, 8001, 8002 ]
connection_max = 5000
enabled = true

[servers]

  # You can indent as you please. Tabs or spaces. TOML don't care.
  [servers.alpha]
  ip = "10.0.0.1"
  dc = "eqdc10"

  [servers.beta]
  ip = "10.0.0.2"
  dc = "eqdc10"

[clients]
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it

# Line breaks are OK when inside arrays
hosts = [
  "alpha",
  "omega"
]

And the corresponding Go types are:

type tomlConfig struct {
	Title string
	Owner ownerInfo
	DB database `toml:"database"`
	Servers map[string]server
	Clients clients
}

type ownerInfo struct {
	Name string
	Org string `toml:"organization"`
	Bio string
	DOB time.Time
}

type database struct {
	Server string
	Ports []int
	ConnMax int `toml:"connection_max"`
	Enabled bool
}

type server struct {
	IP string
	DC string
}

type clients struct {
	Data [][]interface{}
	Hosts []string
}

Note that a case insensitive match will be tried if an exact match can't be found.

A working example of the above can be found in _examples/example.{go,toml}.

Documentation

Overview

Package toml provides facilities for decoding and encoding TOML configuration files via reflection. There is also support for delaying decoding with the Primitive type, and querying the set of keys in a TOML document with the MetaData type.

The specification implemented: https://github.com/toml-lang/toml

The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify whether a file is a valid TOML document. It can also be used to print the type of each key in a TOML document.

Testing

There are two important types of tests used for this package. The first is contained inside '*_test.go' files and uses the standard Go unit testing framework. These tests are primarily devoted to holistically testing the decoder and encoder.

The second type of testing is used to verify the implementation's adherence to the TOML specification. These tests have been factored into their own project: https://github.com/BurntSushi/toml-test

The reason the tests are in a separate project is so that they can be used by any implementation of TOML. Namely, it is language agnostic.

Example (StrictDecoding)

Example StrictDecoding shows how to detect whether there are keys in the TOML document that weren't decoded into the value given. This is useful for returning an error to the user if they've included extraneous fields in their configuration.

var blob = `
key1 = "value1"
key2 = "value2"
key3 = "value3"
`
type config struct {
	Key1 string
	Key3 string
}

var conf config
md, err := Decode(blob, &conf)
if err != nil {
	log.Fatal(err)
}
fmt.Printf("Undecoded keys: %q\n", md.Undecoded())
Output:
Undecoded keys: ["key2"]
Example (UnmarshalTOML)

Example UnmarshalTOML shows how to implement a struct type that knows how to unmarshal itself. The struct must take full responsibility for mapping the values passed into the struct. The method may be used with interfaces in a struct in cases where the actual type is not known until the data is examined.

var blob = `
[[parts]]
type = "valve"
id = "valve-1"
size = 1.2
rating = 4

[[parts]]
type = "valve"
id = "valve-2"
size = 2.1
rating = 5

[[parts]]
type = "pipe"
id = "pipe-1"
length = 2.1
diameter = 12

[[parts]]
type = "cable"
id = "cable-1"
length = 12
rating = 3.1
`
o := &order{}
err := Unmarshal([]byte(blob), o)
if err != nil {
	log.Fatal(err)
}

fmt.Println(len(o.parts))

for _, part := range o.parts {
	fmt.Println(part.Name())
}

// Code to implement UmarshalJSON.

// type order struct {
// 	// NOTE `order.parts` is a private slice of type `part` which is an
// 	// interface and may only be loaded from toml using the
// 	// UnmarshalTOML() method of the Umarshaler interface.
// 	parts parts
// }

// func (o *order) UnmarshalTOML(data interface{}) error {

// 	// NOTE the example below contains detailed type casting to show how
// 	// the 'data' is retrieved. In operational use, a type cast wrapper
// 	// may be preferred e.g.
// 	//
// 	// func AsMap(v interface{}) (map[string]interface{}, error) {
// 	// 		return v.(map[string]interface{})
// 	// }
// 	//
// 	// resulting in:
// 	// d, _ := AsMap(data)
// 	//

// 	d, _ := data.(map[string]interface{})
// 	parts, _ := d["parts"].([]map[string]interface{})

// 	for _, p := range parts {

// 		typ, _ := p["type"].(string)
// 		id, _ := p["id"].(string)

// 		// detect the type of part and handle each case
// 		switch p["type"] {
// 		case "valve":

// 			size := float32(p["size"].(float64))
// 			rating := int(p["rating"].(int64))

// 			valve := &valve{
// 				Type:   typ,
// 				ID:     id,
// 				Size:   size,
// 				Rating: rating,
// 			}

// 			o.parts = append(o.parts, valve)

// 		case "pipe":

// 			length := float32(p["length"].(float64))
// 			diameter := int(p["diameter"].(int64))

// 			pipe := &pipe{
// 				Type:     typ,
// 				ID:       id,
// 				Length:   length,
// 				Diameter: diameter,
// 			}

// 			o.parts = append(o.parts, pipe)

// 		case "cable":

// 			length := int(p["length"].(int64))
// 			rating := float32(p["rating"].(float64))

// 			cable := &cable{
// 				Type:   typ,
// 				ID:     id,
// 				Length: length,
// 				Rating: rating,
// 			}

// 			o.parts = append(o.parts, cable)

// 		}
// 	}

// 	return nil
// }

// type parts []part

// type part interface {
// 	Name() string
// }

// type valve struct {
// 	Type   string
// 	ID     string
// 	Size   float32
// 	Rating int
// }

// func (v *valve) Name() string {
// 	return fmt.Sprintf("VALVE: %s", v.ID)
// }

// type pipe struct {
// 	Type     string
// 	ID       string
// 	Length   float32
// 	Diameter int
// }

// func (p *pipe) Name() string {
// 	return fmt.Sprintf("PIPE: %s", p.ID)
// }

// type cable struct {
// 	Type   string
// 	ID     string
// 	Length int
// 	Rating float32
// }

// func (c *cable) Name() string {
// 	return fmt.Sprintf("CABLE: %s", c.ID)
// }
Output:
4
VALVE: valve-1
VALVE: valve-2
PIPE: pipe-1
CABLE: cable-1
Example (Unmarshaler)

Example Unmarshaler shows how to decode TOML strings into your own custom data type.

blob := `
[[song]]
name = "Thunder Road"
duration = "4m49s"

[[song]]
name = "Stairway to Heaven"
duration = "8m03s"
`
type song struct {
	Name     string
	Duration duration
}
type songs struct {
	Song []song
}
var favorites songs
if _, err := Decode(blob, &favorites); err != nil {
	log.Fatal(err)
}

// Code to implement the TextUnmarshaler interface for `duration`:
//
// type duration struct {
// 	time.Duration
// }
//
// func (d *duration) UnmarshalText(text []byte) error {
// 	var err error
// 	d.Duration, err = time.ParseDuration(string(text))
// 	return err
// }

for _, s := range favorites.Song {
	fmt.Printf("%s (%s)\n", s.Name, s.Duration)
}
Output:
Thunder Road (4m49s)
Stairway to Heaven (8m3s)

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func PrimitiveDecode

func PrimitiveDecode(primValue Primitive, v interface{}) error

DEPRECATED!

Use MetaData.PrimitiveDecode instead.

func Unmarshal