Bump github.com/BurntSushi/toml from 1.1.0 to 1.2.0

Bumps [github.com/BurntSushi/toml](https://github.com/BurntSushi/toml) from 1.1.0 to 1.2.0.
- [Release notes](https://github.com/BurntSushi/toml/releases)
- [Commits](https://github.com/BurntSushi/toml/compare/v1.1.0...v1.2.0)

---
updated-dependencies:
- dependency-name: github.com/BurntSushi/toml
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2022-07-21 12:11:35 +00:00
committed by GitHub
parent 712267ee20
commit 138d185cc5
12 changed files with 410 additions and 351 deletions

2
go.mod
View File

@ -3,7 +3,7 @@ module github.com/containers/podman/v4
go 1.16 go 1.16
require ( require (
github.com/BurntSushi/toml v1.1.0 github.com/BurntSushi/toml v1.2.0
github.com/blang/semver/v4 v4.0.0 github.com/blang/semver/v4 v4.0.0
github.com/buger/goterm v1.0.4 github.com/buger/goterm v1.0.4
github.com/checkpoint-restore/checkpointctl v0.0.0-20220321135231-33f4a66335f0 github.com/checkpoint-restore/checkpointctl v0.0.0-20220321135231-33f4a66335f0

3
go.sum
View File

@ -102,8 +102,9 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=
github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=

View File

@ -1,2 +1,2 @@
toml.test /toml.test
/toml-test /toml-test

View File

@ -1 +0,0 @@
Compatible with TOML version [v1.0.0](https://toml.io/en/v1.0.0).

View File

@ -1,6 +1,5 @@
TOML stands for Tom's Obvious, Minimal Language. This Go package provides a TOML stands for Tom's Obvious, Minimal Language. This Go package provides a
reflection interface similar to Go's standard library `json` and `xml` reflection interface similar to Go's standard library `json` and `xml` packages.
packages.
Compatible with TOML version [v1.0.0](https://toml.io/en/v1.0.0). Compatible with TOML version [v1.0.0](https://toml.io/en/v1.0.0).
@ -10,7 +9,7 @@ See the [releases page](https://github.com/BurntSushi/toml/releases) for a
changelog; this information is also in the git tag annotations (e.g. `git show changelog; this information is also in the git tag annotations (e.g. `git show
v0.4.0`). v0.4.0`).
This library requires Go 1.13 or newer; install it with: This library requires Go 1.13 or newer; add it to your go.mod with:
% go get github.com/BurntSushi/toml@latest % go get github.com/BurntSushi/toml@latest
@ -19,16 +18,7 @@ It also comes with a TOML validator CLI tool:
% go install github.com/BurntSushi/toml/cmd/tomlv@latest % go install github.com/BurntSushi/toml/cmd/tomlv@latest
% tomlv some-toml-file.toml % tomlv some-toml-file.toml
### Testing
This package passes all tests in [toml-test] for both the decoder and the
encoder.
[toml-test]: https://github.com/BurntSushi/toml-test
### Examples ### Examples
This package works similar 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 For the simplest example, consider some TOML file as just a list of keys and
values: values:
@ -40,7 +30,7 @@ Perfection = [ 6, 28, 496, 8128 ]
DOB = 1987-07-05T05:45:00Z DOB = 1987-07-05T05:45:00Z
``` ```
Which could be defined in Go as: Which can be decoded with:
```go ```go
type Config struct { type Config struct {
@ -48,20 +38,15 @@ type Config struct {
Cats []string Cats []string
Pi float64 Pi float64
Perfection []int Perfection []int
DOB time.Time // requires `import time` DOB time.Time
} }
```
And then decoded with:
```go
var conf Config var conf Config
_, err := toml.Decode(tomlData, &conf) _, err := toml.Decode(tomlData, &conf)
// handle error
``` ```
You can also use struct tags if your struct field name doesn't map to a TOML You can also use struct tags if your struct field name doesn't map to a TOML key
key value directly: value directly:
```toml ```toml
some_key_NAME = "wat" some_key_NAME = "wat"
@ -73,139 +58,63 @@ type TOML struct {
} }
``` ```
Beware that like other most other decoders **only exported fields** are Beware that like other decoders **only exported fields** are considered when
considered when encoding and decoding; private fields are silently ignored. encoding and decoding; private fields are silently ignored.
### Using the `Marshaler` and `encoding.TextUnmarshaler` interfaces ### Using the `Marshaler` and `encoding.TextUnmarshaler` interfaces
Here's an example that automatically parses duration strings into Here's an example that automatically parses values in a `mail.Address`:
`time.Duration` values:
```toml ```toml
[[song]] contacts = [
name = "Thunder Road" "Donald Duck <donald@duckburg.com>",
duration = "4m49s" "Scrooge McDuck <scrooge@duckburg.com>",
]
[[song]]
name = "Stairway to Heaven"
duration = "8m03s"
``` ```
Which can be decoded with: Can be decoded with:
```go ```go
type song struct { // Create address type which satisfies the encoding.TextUnmarshaler interface.
Name string type address struct {
Duration duration *mail.Address
}
type songs struct {
Song []song
}
var favorites songs
if _, err := toml.Decode(blob, &favorites); err != nil {
log.Fatal(err)
} }
for _, s := range favorites.Song { func (a *address) UnmarshalText(text []byte) error {
fmt.Printf("%s (%s)\n", s.Name, s.Duration)
}
```
And you'll also need a `duration` type that satisfies the
`encoding.TextUnmarshaler` interface:
```go
type duration struct {
time.Duration
}
func (d *duration) UnmarshalText(text []byte) error {
var err error var err error
d.Duration, err = time.ParseDuration(string(text)) a.Address, err = mail.ParseAddress(string(text))
return err return err
} }
// Decode it.
func decode() {
blob := `
contacts = [
"Donald Duck <donald@duckburg.com>",
"Scrooge McDuck <scrooge@duckburg.com>",
]
`
var contacts struct {
Contacts []address
}
_, err := toml.Decode(blob, &contacts)
if err != nil {
log.Fatal(err)
}
for _, c := range contacts.Contacts {
fmt.Printf("%#v\n", c.Address)
}
// Output:
// &mail.Address{Name:"Donald Duck", Address:"donald@duckburg.com"}
// &mail.Address{Name:"Scrooge McDuck", Address:"scrooge@duckburg.com"}
}
``` ```
To target TOML specifically you can implement `UnmarshalTOML` TOML interface in To target TOML specifically you can implement `UnmarshalTOML` TOML interface in
a similar way. a similar way.
### More complex usage ### More complex usage
Here's an example of how to load the example from the official spec page: See the [`_example/`](/_example) directory for a more complex example.
```toml
# 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:
```go
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 `_example/example.{go,toml}`.

View File

@ -3,13 +3,16 @@ package toml
import ( import (
"bytes" "bytes"
"encoding" "encoding"
"encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"math" "math"
"os" "os"
"reflect" "reflect"
"strconv"
"strings" "strings"
"time"
) )
// Unmarshaler is the interface implemented by objects that can unmarshal a // Unmarshaler is the interface implemented by objects that can unmarshal a
@ -18,7 +21,7 @@ type Unmarshaler interface {
UnmarshalTOML(interface{}) error UnmarshalTOML(interface{}) error
} }
// Unmarshal decodes the contents of `p` in TOML format into a pointer `v`. // Unmarshal decodes the contents of `data` in TOML format into a pointer `v`.
func Unmarshal(data []byte, v interface{}) error { func Unmarshal(data []byte, v interface{}) error {
_, err := NewDecoder(bytes.NewReader(data)).Decode(v) _, err := NewDecoder(bytes.NewReader(data)).Decode(v)
return err return err
@ -75,6 +78,9 @@ const (
// TOML datetimes correspond to Go time.Time values. Local datetimes are parsed // TOML datetimes correspond to Go time.Time values. Local datetimes are parsed
// in the local timezone. // in the local timezone.
// //
// time.Duration types are treated as nanoseconds if the TOML value is an
// integer, or they're parsed with time.ParseDuration() if they're strings.
//
// All other TOML types (float, string, int, bool and array) correspond to the // All other TOML types (float, string, int, bool and array) correspond to the
// obvious Go types. // obvious Go types.
// //
@ -82,7 +88,7 @@ const (
// interface, in which case any primitive TOML value (floats, strings, integers, // interface, in which case any primitive TOML value (floats, strings, integers,
// booleans, datetimes) will be converted to a []byte and given to the value's // booleans, datetimes) will be converted to a []byte and given to the value's
// UnmarshalText method. See the Unmarshaler example for a demonstration with // UnmarshalText method. See the Unmarshaler example for a demonstration with
// time duration strings. // email addresses.
// //
// Key mapping // Key mapping
// //
@ -111,6 +117,7 @@ func NewDecoder(r io.Reader) *Decoder {
var ( var (
unmarshalToml = reflect.TypeOf((*Unmarshaler)(nil)).Elem() unmarshalToml = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
unmarshalText = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem() unmarshalText = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
primitiveType = reflect.TypeOf((*Primitive)(nil)).Elem()
) )
// Decode TOML data in to the pointer `v`. // Decode TOML data in to the pointer `v`.
@ -122,10 +129,10 @@ func (dec *Decoder) Decode(v interface{}) (MetaData, error) {
s = "%v" s = "%v"
} }
return MetaData{}, e("cannot decode to non-pointer "+s, reflect.TypeOf(v)) return MetaData{}, fmt.Errorf("toml: cannot decode to non-pointer "+s, reflect.TypeOf(v))
} }
if rv.IsNil() { if rv.IsNil() {
return MetaData{}, e("cannot decode to nil value of %q", reflect.TypeOf(v)) return MetaData{}, fmt.Errorf("toml: cannot decode to nil value of %q", reflect.TypeOf(v))
} }
// Check if this is a supported type: struct, map, interface{}, or something // Check if this is a supported type: struct, map, interface{}, or something
@ -135,7 +142,7 @@ func (dec *Decoder) Decode(v interface{}) (MetaData, error) {
if rv.Kind() != reflect.Struct && rv.Kind() != reflect.Map && if rv.Kind() != reflect.Struct && rv.Kind() != reflect.Map &&
!(rv.Kind() == reflect.Interface && rv.NumMethod() == 0) && !(rv.Kind() == reflect.Interface && rv.NumMethod() == 0) &&
!rt.Implements(unmarshalToml) && !rt.Implements(unmarshalText) { !rt.Implements(unmarshalToml) && !rt.Implements(unmarshalText) {
return MetaData{}, e("cannot decode to type %s", rt) return MetaData{}, fmt.Errorf("toml: cannot decode to type %s", rt)
} }
// TODO: parser should read from io.Reader? Or at the very least, make it // TODO: parser should read from io.Reader? Or at the very least, make it
@ -152,10 +159,11 @@ func (dec *Decoder) Decode(v interface{}) (MetaData, error) {
md := MetaData{ md := MetaData{
mapping: p.mapping, mapping: p.mapping,
types: p.types, keyInfo: p.keyInfo,
keys: p.ordered, keys: p.ordered,
decoded: make(map[string]struct{}, len(p.ordered)), decoded: make(map[string]struct{}, len(p.ordered)),
context: nil, context: nil,
data: data,
} }
return md, md.unify(p.mapping, rv) return md, md.unify(p.mapping, rv)
} }
@ -185,7 +193,7 @@ func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error {
func (md *MetaData) unify(data interface{}, rv reflect.Value) error { func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
// Special case. Look for a `Primitive` value. // Special case. Look for a `Primitive` value.
// TODO: #76 would make this superfluous after implemented. // TODO: #76 would make this superfluous after implemented.
if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() { if rv.Type() == primitiveType {
// Save the undecoded data and the key context into the primitive // Save the undecoded data and the key context into the primitive
// value. // value.
context := make(Key, len(md.context)) context := make(Key, len(md.context))
@ -197,17 +205,14 @@ func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
return nil return nil
} }
// Special case. Unmarshaler Interface support. rvi := rv.Interface()
if rv.CanAddr() { if v, ok := rvi.(Unmarshaler); ok {
if v, ok := rv.Addr().Interface().(Unmarshaler); ok { return v.UnmarshalTOML(data)
return v.UnmarshalTOML(data)
}
} }
if v, ok := rvi.(encoding.TextUnmarshaler); ok {
// Special case. Look for a value satisfying the TextUnmarshaler interface.
if v, ok := rv.Interface().(encoding.TextUnmarshaler); ok {
return md.unifyText(data, v) return md.unifyText(data, v)
} }
// TODO: // TODO:
// The behavior here is incorrect whenever a Go type satisfies the // The behavior here is incorrect whenever a Go type satisfies the
// encoding.TextUnmarshaler interface but also corresponds to a TOML hash or // encoding.TextUnmarshaler interface but also corresponds to a TOML hash or
@ -218,7 +223,6 @@ func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
k := rv.Kind() k := rv.Kind()
// laziness
if k >= reflect.Int && k <= reflect.Uint64 { if k >= reflect.Int && k <= reflect.Uint64 {
return md.unifyInt(data, rv) return md.unifyInt(data, rv)
} }
@ -244,15 +248,14 @@ func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
case reflect.Bool: case reflect.Bool:
return md.unifyBool(data, rv) return md.unifyBool(data, rv)
case reflect.Interface: case reflect.Interface:
// we only support empty interfaces. if rv.NumMethod() > 0 { // Only support empty interfaces are supported.
if rv.NumMethod() > 0 { return md.e("unsupported type %s", rv.Type())
return e("unsupported type %s", rv.Type())
} }
return md.unifyAnything(data, rv) return md.unifyAnything(data, rv)
case reflect.Float32, reflect.Float64: case reflect.Float32, reflect.Float64:
return md.unifyFloat64(data, rv) return md.unifyFloat64(data, rv)
} }
return e("unsupported type %s", rv.Kind()) return md.e("unsupported type %s", rv.Kind())
} }
func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error { func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
@ -261,7 +264,7 @@ func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
if mapping == nil { if mapping == nil {
return nil return nil
} }
return e("type mismatch for %s: expected table but found %T", return md.e("type mismatch for %s: expected table but found %T",
rv.Type().String(), mapping) rv.Type().String(), mapping)
} }
@ -287,13 +290,14 @@ func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
if isUnifiable(subv) { if isUnifiable(subv) {
md.decoded[md.context.add(key).String()] = struct{}{} md.decoded[md.context.add(key).String()] = struct{}{}
md.context = append(md.context, key) md.context = append(md.context, key)
err := md.unify(datum, subv) err := md.unify(datum, subv)
if err != nil { if err != nil {
return err return err
} }
md.context = md.context[0 : len(md.context)-1] md.context = md.context[0 : len(md.context)-1]
} else if f.name != "" { } else if f.name != "" {
return e("cannot write unexported field %s.%s", rv.Type().String(), f.name) return md.e("cannot write unexported field %s.%s", rv.Type().String(), f.name)
} }
} }
} }
@ -301,10 +305,10 @@ func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
} }
func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error { func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
if k := rv.Type().Key().Kind(); k != reflect.String { keyType := rv.Type().Key().Kind()
return fmt.Errorf( if keyType != reflect.String && keyType != reflect.Interface {
"toml: cannot decode to a map with non-string key type (%s in %q)", return fmt.Errorf("toml: cannot decode to a map with non-string key type (%s in %q)",
k, rv.Type()) keyType, rv.Type())
} }
tmap, ok := mapping.(map[string]interface{}) tmap, ok := mapping.(map[string]interface{})
@ -322,13 +326,22 @@ func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
md.context = append(md.context, k) md.context = append(md.context, k)
rvval := reflect.Indirect(reflect.New(rv.Type().Elem())) rvval := reflect.Indirect(reflect.New(rv.Type().Elem()))
if err := md.unify(v, rvval); err != nil {
err := md.unify(v, indirect(rvval))
if err != nil {
return err return err
} }
md.context = md.context[0 : len(md.context)-1] md.context = md.context[0 : len(md.context)-1]
rvkey := indirect(reflect.New(rv.Type().Key())) rvkey := indirect(reflect.New(rv.Type().Key()))
rvkey.SetString(k)
switch keyType {
case reflect.Interface:
rvkey.Set(reflect.ValueOf(k))
case reflect.String:
rvkey.SetString(k)
}
rv.SetMapIndex(rvkey, rvval) rv.SetMapIndex(rvkey, rvval)
} }
return nil return nil
@ -343,7 +356,7 @@ func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
return md.badtype("slice", data) return md.badtype("slice", data)
} }
if l := datav.Len(); l != rv.Len() { if l := datav.Len(); l != rv.Len() {
return e("expected array length %d; got TOML array of length %d", rv.Len(), l) return md.e("expected array length %d; got TOML array of length %d", rv.Len(), l)
} }
return md.unifySliceArray(datav, rv) return md.unifySliceArray(datav, rv)
} }
@ -376,6 +389,18 @@ func (md *MetaData) unifySliceArray(data, rv reflect.Value) error {
} }
func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error { func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error {
_, ok := rv.Interface().(json.Number)
if ok {
if i, ok := data.(int64); ok {
rv.SetString(strconv.FormatInt(i, 10))
} else if f, ok := data.(float64); ok {
rv.SetString(strconv.FormatFloat(f, 'f', -1, 64))
} else {
return md.badtype("string", data)
}
return nil
}
if s, ok := data.(string); ok { if s, ok := data.(string); ok {
rv.SetString(s) rv.SetString(s)
return nil return nil
@ -384,11 +409,13 @@ func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error {
} }
func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error { func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error {
rvk := rv.Kind()
if num, ok := data.(float64); ok { if num, ok := data.(float64); ok {
switch rv.Kind() { switch rvk {
case reflect.Float32: case reflect.Float32:
if num < -math.MaxFloat32 || num > math.MaxFloat32 { if num < -math.MaxFloat32 || num > math.MaxFloat32 {
return e("value %f is out of range for float32", num) return md.parseErr(errParseRange{i: num, size: rvk.String()})
} }
fallthrough fallthrough
case reflect.Float64: case reflect.Float64:
@ -400,20 +427,11 @@ func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error {
} }
if num, ok := data.(int64); ok { if num, ok := data.(int64); ok {
switch rv.Kind() { if (rvk == reflect.Float32 && (num < -maxSafeFloat32Int || num > maxSafeFloat32Int)) ||
case reflect.Float32: (rvk == reflect.Float64 && (num < -maxSafeFloat64Int || num > maxSafeFloat64Int)) {
if num < -maxSafeFloat32Int || num > maxSafeFloat32Int { return md.parseErr(errParseRange{i: num, size: rvk.String()})
return e("value %d is out of range for float32", num)
}
fallthrough
case reflect.Float64:
if num < -maxSafeFloat64Int || num > maxSafeFloat64Int {
return e("value %d is out of range for float64", num)
}
rv.SetFloat(float64(num))
default:
panic("bug")
} }
rv.SetFloat(float64(num))
return nil return nil
} }
@ -421,50 +439,46 @@ func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error {
} }
func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error { func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error {
if num, ok := data.(int64); ok { _, ok := rv.Interface().(time.Duration)
if rv.Kind() >= reflect.Int && rv.Kind() <= reflect.Int64 { if ok {
switch rv.Kind() { // Parse as string duration, and fall back to regular integer parsing
case reflect.Int, reflect.Int64: // (as nanosecond) if this is not a string.
// No bounds checking necessary. if s, ok := data.(string); ok {
case reflect.Int8: dur, err := time.ParseDuration(s)
if num < math.MinInt8 || num > math.MaxInt8 { if err != nil {
return e("value %d is out of range for int8", num) return md.parseErr(errParseDuration{s})
}
case reflect.Int16:
if num < math.MinInt16 || num > math.MaxInt16 {
return e("value %d is out of range for int16", num)
}
case reflect.Int32:
if num < math.MinInt32 || num > math.MaxInt32 {
return e("value %d is out of range for int32", num)
}
} }
rv.SetInt(num) rv.SetInt(int64(dur))
} else if rv.Kind() >= reflect.Uint && rv.Kind() <= reflect.Uint64 { return nil
unum := uint64(num)
switch rv.Kind() {
case reflect.Uint, reflect.Uint64:
// No bounds checking necessary.
case reflect.Uint8:
if num < 0 || unum > math.MaxUint8 {
return e("value %d is out of range for uint8", num)
}
case reflect.Uint16:
if num < 0 || unum > math.MaxUint16 {
return e("value %d is out of range for uint16", num)
}
case reflect.Uint32:
if num < 0 || unum > math.MaxUint32 {
return e("value %d is out of range for uint32", num)
}
}
rv.SetUint(unum)
} else {
panic("unreachable")
} }
return nil
} }
return md.badtype("integer", data)
num, ok := data.(int64)
if !ok {
return md.badtype("integer", data)
}
rvk := rv.Kind()
switch {
case rvk >= reflect.Int && rvk <= reflect.Int64:
if (rvk == reflect.Int8 && (num < math.MinInt8 || num > math.MaxInt8)) ||
(rvk == reflect.Int16 && (num < math.MinInt16 || num > math.MaxInt16)) ||
(rvk == reflect.Int32 && (num < math.MinInt32 || num > math.MaxInt32)) {
return md.parseErr(errParseRange{i: num, size: rvk.String()})
}
rv.SetInt(num)
case rvk >= reflect.Uint && rvk <= reflect.Uint64:
unum := uint64(num)
if rvk == reflect.Uint8 && (num < 0 || unum > math.MaxUint8) ||
rvk == reflect.Uint16 && (num < 0 || unum > math.MaxUint16) ||
rvk == reflect.Uint32 && (num < 0 || unum > math.MaxUint32) {
return md.parseErr(errParseRange{i: num, size: rvk.String()})
}
rv.SetUint(unum)
default:
panic("unreachable")
}
return nil
} }
func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error { func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error {
@ -489,7 +503,7 @@ func (md *MetaData) unifyText(data interface{}, v encoding.TextUnmarshaler) erro
return err return err
} }
s = string(text) s = string(text)
case TextMarshaler: case encoding.TextMarshaler:
text, err := sdata.MarshalText() text, err := sdata.MarshalText()
if err != nil { if err != nil {
return err return err
@ -515,7 +529,30 @@ func (md *MetaData) unifyText(data interface{}, v encoding.TextUnmarshaler) erro
} }
func (md *MetaData) badtype(dst string, data interface{}) error { func (md *MetaData) badtype(dst string, data interface{}) error {
return e("incompatible types: TOML key %q has type %T; destination has type %s", md.context, data, dst) return md.e("incompatible types: TOML value has type %T; destination has type %s", data, dst)
}
func (md *MetaData) parseErr(err error) error {
k := md.context.String()
return ParseError{
LastKey: k,
Position: md.keyInfo[k].pos,
Line: md.keyInfo[k].pos.Line,
err: err,
input: string(md.data),
}
}
func (md *MetaData) e(format string, args ...interface{}) error {
f := "toml: "
if len(md.context) > 0 {
f = fmt.Sprintf("toml: (last key %q): ", md.context)
p := md.keyInfo[md.context.String()].pos
if p.Line > 0 {
f = fmt.Sprintf("toml: line %d (last key %q): ", p.Line, md.context)
}
}
return fmt.Errorf(f+format, args...)
} }
// rvalue returns a reflect.Value of `v`. All pointers are resolved. // rvalue returns a reflect.Value of `v`. All pointers are resolved.
@ -534,7 +571,11 @@ func indirect(v reflect.Value) reflect.Value {
if v.Kind() != reflect.Ptr { if v.Kind() != reflect.Ptr {
if v.CanSet() { if v.CanSet() {
pv := v.Addr() pv := v.Addr()
if _, ok := pv.Interface().(encoding.TextUnmarshaler); ok { pvi := pv.Interface()
if _, ok := pvi.(encoding.TextUnmarshaler); ok {
return pv
}
if _, ok := pvi.(Unmarshaler); ok {
return pv return pv
} }
} }
@ -550,12 +591,12 @@ func isUnifiable(rv reflect.Value) bool {
if rv.CanSet() { if rv.CanSet() {
return true return true
} }
if _, ok := rv.Interface().(encoding.TextUnmarshaler); ok { rvi := rv.Interface()
if _, ok := rvi.(encoding.TextUnmarshaler); ok {
return true
}
if _, ok := rvi.(Unmarshaler); ok {
return true return true
} }
return false return false
} }
func e(format string, args ...interface{}) error {
return fmt.Errorf("toml: "+format, args...)
}

View File

@ -3,6 +3,7 @@ package toml
import ( import (
"bufio" "bufio"
"encoding" "encoding"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -63,6 +64,12 @@ var dblQuotedReplacer = strings.NewReplacer(
"\x7f", `\u007f`, "\x7f", `\u007f`,
) )
var (
marshalToml = reflect.TypeOf((*Marshaler)(nil)).Elem()
marshalText = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
timeType = reflect.TypeOf((*time.Time)(nil)).Elem()
)
// Marshaler is the interface implemented by types that can marshal themselves // Marshaler is the interface implemented by types that can marshal themselves
// into valid TOML. // into valid TOML.
type Marshaler interface { type Marshaler interface {
@ -74,6 +81,9 @@ type Marshaler interface {
// The mapping between Go values and TOML values should be precisely the same as // The mapping between Go values and TOML values should be precisely the same as
// for the Decode* functions. // for the Decode* functions.
// //
// time.Time is encoded as a RFC 3339 string, and time.Duration as its string
// representation.
//
// The toml.Marshaler and encoder.TextMarshaler interfaces are supported to // The toml.Marshaler and encoder.TextMarshaler interfaces are supported to
// encoding the value as custom TOML. // encoding the value as custom TOML.
// //
@ -85,6 +95,17 @@ type Marshaler interface {
// //
// Go maps will be sorted alphabetically by key for deterministic output. // Go maps will be sorted alphabetically by key for deterministic output.
// //
// The toml struct tag can be used to provide the key name; if omitted the
// struct field name will be used. If the "omitempty" option is present the
// following value will be skipped:
//
// - arrays, slices, maps, and string with len of 0
// - struct with all zero values
// - bool false
//
// If omitzero is given all int and float types with a value of 0 will be
// skipped.
//
// Encoding Go values without a corresponding TOML representation will return an // Encoding Go values without a corresponding TOML representation will return an
// error. Examples of this includes maps with non-string keys, slices with nil // error. Examples of this includes maps with non-string keys, slices with nil
// elements, embedded non-struct types, and nested slices containing maps or // elements, embedded non-struct types, and nested slices containing maps or
@ -136,18 +157,15 @@ func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) {
} }
func (enc *Encoder) encode(key Key, rv reflect.Value) { func (enc *Encoder) encode(key Key, rv reflect.Value) {
// Special case: time needs to be in ISO8601 format. // If we can marshal the type to text, then we use that. This prevents the
// // encoder for handling these types as generic structs (or whatever the
// Special case: if we can marshal the type to text, then we used that. This // underlying type of a TextMarshaler is).
// prevents the encoder for handling these types as generic structs (or switch {
// whatever the underlying type of a TextMarshaler is). case isMarshaler(rv):
switch t := rv.Interface().(type) {
case time.Time, encoding.TextMarshaler, Marshaler:
enc.writeKeyValue(key, rv, false) enc.writeKeyValue(key, rv, false)
return return
// TODO: #76 would make this superfluous after implemented. case rv.Type() == primitiveType: // TODO: #76 would make this superfluous after implemented.
case Primitive: enc.encode(key, reflect.ValueOf(rv.Interface().(Primitive).undecoded))
enc.encode(key, reflect.ValueOf(t.undecoded))
return return
} }
@ -212,6 +230,9 @@ func (enc *Encoder) eElement(rv reflect.Value) {
if err != nil { if err != nil {
encPanic(err) encPanic(err)
} }
if s == nil {
encPanic(errors.New("MarshalTOML returned nil and no error"))
}
enc.w.Write(s) enc.w.Write(s)
return return
case encoding.TextMarshaler: case encoding.TextMarshaler:
@ -219,11 +240,34 @@ func (enc *Encoder) eElement(rv reflect.Value) {
if err != nil { if err != nil {
encPanic(err) encPanic(err)
} }
if s == nil {
encPanic(errors.New("MarshalText returned nil and no error"))
}
enc.writeQuoted(string(s)) enc.writeQuoted(string(s))
return return
case time.Duration:
enc.writeQuoted(v.String())
return
case json.Number:
n, _ := rv.Interface().(json.Number)
if n == "" { /// Useful zero value.
enc.w.WriteByte('0')
return
} else if v, err := n.Int64(); err == nil {
enc.eElement(reflect.ValueOf(v))
return
} else if v, err := n.Float64(); err == nil {
enc.eElement(reflect.ValueOf(v))
return
}
encPanic(errors.New(fmt.Sprintf("Unable to convert \"%s\" to neither int64 nor float64", n)))
} }
switch rv.Kind() { switch rv.Kind() {
case reflect.Ptr:
enc.eElement(rv.Elem())
return
case reflect.String: case reflect.String:
enc.writeQuoted(rv.String()) enc.writeQuoted(rv.String())
case reflect.Bool: case reflect.Bool:
@ -259,7 +303,7 @@ func (enc *Encoder) eElement(rv reflect.Value) {
case reflect.Interface: case reflect.Interface:
enc.eElement(rv.Elem()) enc.eElement(rv.Elem())
default: default:
encPanic(fmt.Errorf("unexpected primitive type: %T", rv.Interface())) encPanic(fmt.Errorf("unexpected type: %T", rv.Interface()))
} }
} }
@ -280,7 +324,7 @@ func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) {
length := rv.Len() length := rv.Len()
enc.wf("[") enc.wf("[")
for i := 0; i < length; i++ { for i := 0; i < length; i++ {
elem := rv.Index(i) elem := eindirect(rv.Index(i))
enc.eElement(elem) enc.eElement(elem)
if i != length-1 { if i != length-1 {
enc.wf(", ") enc.wf(", ")
@ -294,7 +338,7 @@ func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
encPanic(errNoKey) encPanic(errNoKey)
} }
for i := 0; i < rv.Len(); i++ { for i := 0; i < rv.Len(); i++ {
trv := rv.Index(i) trv := eindirect(rv.Index(i))
if isNil(trv) { if isNil(trv) {
continue continue
} }
@ -319,7 +363,7 @@ func (enc *Encoder) eTable(key Key, rv reflect.Value) {
} }
func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value, inline bool) { func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value, inline bool) {
switch rv := eindirect(rv); rv.Kind() { switch rv.Kind() {
case reflect.Map: case reflect.Map:
enc.eMap(key, rv, inline) enc.eMap(key, rv, inline)
case reflect.Struct: case reflect.Struct:
@ -341,7 +385,7 @@ func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) {
var mapKeysDirect, mapKeysSub []string var mapKeysDirect, mapKeysSub []string
for _, mapKey := range rv.MapKeys() { for _, mapKey := range rv.MapKeys() {
k := mapKey.String() k := mapKey.String()
if typeIsTable(tomlTypeOfGo(rv.MapIndex(mapKey))) { if typeIsTable(tomlTypeOfGo(eindirect(rv.MapIndex(mapKey)))) {
mapKeysSub = append(mapKeysSub, k) mapKeysSub = append(mapKeysSub, k)
} else { } else {
mapKeysDirect = append(mapKeysDirect, k) mapKeysDirect = append(mapKeysDirect, k)
@ -351,7 +395,7 @@ func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) {
var writeMapKeys = func(mapKeys []string, trailC bool) { var writeMapKeys = func(mapKeys []string, trailC bool) {
sort.Strings(mapKeys) sort.Strings(mapKeys)
for i, mapKey := range mapKeys { for i, mapKey := range mapKeys {
val := rv.MapIndex(reflect.ValueOf(mapKey)) val := eindirect(rv.MapIndex(reflect.ValueOf(mapKey)))
if isNil(val) { if isNil(val) {
continue continue
} }
@ -379,6 +423,13 @@ func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) {
const is32Bit = (32 << (^uint(0) >> 63)) == 32 const is32Bit = (32 << (^uint(0) >> 63)) == 32
func pointerTo(t reflect.Type) reflect.Type {
if t.Kind() == reflect.Ptr {
return pointerTo(t.Elem())
}
return t
}
func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) { func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
// Write keys for fields directly under this key first, because if we write // Write keys for fields directly under this key first, because if we write
// a field that creates a new table then all keys under it will be in that // a field that creates a new table then all keys under it will be in that
@ -395,7 +446,8 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
addFields = func(rt reflect.Type, rv reflect.Value, start []int) { addFields = func(rt reflect.Type, rv reflect.Value, start []int) {
for i := 0; i < rt.NumField(); i++ { for i := 0; i < rt.NumField(); i++ {
f := rt.Field(i) f := rt.Field(i)
if f.PkgPath != "" && !f.Anonymous { /// Skip unexported fields. isEmbed := f.Anonymous && pointerTo(f.Type).Kind() == reflect.Struct
if f.PkgPath != "" && !isEmbed { /// Skip unexported fields.
continue continue
} }
opts := getOptions(f.Tag) opts := getOptions(f.Tag)
@ -403,27 +455,16 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
continue continue
} }
frv := rv.Field(i) frv := eindirect(rv.Field(i))
// Treat anonymous struct fields with tag names as though they are // Treat anonymous struct fields with tag names as though they are
// not anonymous, like encoding/json does. // not anonymous, like encoding/json does.
// //
// Non-struct anonymous fields use the normal encoding logic. // Non-struct anonymous fields use the normal encoding logic.
if f.Anonymous { if isEmbed {
t := f.Type if getOptions(f.Tag).name == "" && frv.Kind() == reflect.Struct {
switch t.Kind() { addFields(frv.Type(), frv, append(start, f.Index...))
case reflect.Struct: continue
if getOptions(f.Tag).name == "" {
addFields(t, frv, append(start, f.Index...))
continue
}
case reflect.Ptr:
if t.Elem().Kind() == reflect.Struct && getOptions(f.Tag).name == "" {
if !frv.IsNil() {
addFields(t.Elem(), frv.Elem(), append(start, f.Index...))
}
continue
}
} }
} }
@ -449,7 +490,7 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
writeFields := func(fields [][]int) { writeFields := func(fields [][]int) {
for _, fieldIndex := range fields { for _, fieldIndex := range fields {
fieldType := rt.FieldByIndex(fieldIndex) fieldType := rt.FieldByIndex(fieldIndex)
fieldVal := rv.FieldByIndex(fieldIndex) fieldVal := eindirect(rv.FieldByIndex(fieldIndex))
if isNil(fieldVal) { /// Don't write anything for nil fields. if isNil(fieldVal) { /// Don't write anything for nil fields.
continue continue
@ -502,6 +543,21 @@ func tomlTypeOfGo(rv reflect.Value) tomlType {
if isNil(rv) || !rv.IsValid() { if isNil(rv) || !rv.IsValid() {
return nil return nil
} }
if rv.Kind() == reflect.Struct {
if rv.Type() == timeType {
return tomlDatetime
}
if isMarshaler(rv) {
return tomlString
}
return tomlHash
}
if isMarshaler(rv) {
return tomlString
}
switch rv.Kind() { switch rv.Kind() {
case reflect.Bool: case reflect.Bool:
return tomlBool return tomlBool
@ -513,7 +569,7 @@ func tomlTypeOfGo(rv reflect.Value) tomlType {
case reflect.Float32, reflect.Float64: case reflect.Float32, reflect.Float64:
return tomlFloat return tomlFloat
case reflect.Array, reflect.Slice: case reflect.Array, reflect.Slice:
if typeEqual(tomlHash, tomlArrayType(rv)) { if isTableArray(rv) {
return tomlArrayHash return tomlArrayHash
} }
return tomlArray return tomlArray
@ -523,67 +579,35 @@ func tomlTypeOfGo(rv reflect.Value) tomlType {
return tomlString return tomlString
case reflect.Map: case reflect.Map:
return tomlHash return tomlHash
case reflect.Struct:
if _, ok := rv.Interface().(time.Time); ok {
return tomlDatetime
}
if isMarshaler(rv) {
return tomlString
}
return tomlHash
default: default:
if isMarshaler(rv) {
return tomlString
}
encPanic(errors.New("unsupported type: " + rv.Kind().String())) encPanic(errors.New("unsupported type: " + rv.Kind().String()))
panic("unreachable") panic("unreachable")
} }
} }
func isMarshaler(rv reflect.Value) bool { func isMarshaler(rv reflect.Value) bool {
switch rv.Interface().(type) { return rv.Type().Implements(marshalText) || rv.Type().Implements(marshalToml)
case encoding.TextMarshaler:
return true
case Marshaler:
return true
}
// Someone used a pointer receiver: we can make it work for pointer values.
if rv.CanAddr() {
if _, ok := rv.Addr().Interface().(encoding.TextMarshaler); ok {
return true
}
if _, ok := rv.Addr().Interface().(Marshaler); ok {
return true
}
}
return false
} }
// tomlArrayType returns the element type of a TOML array. The type returned // isTableArray reports if all entries in the array or slice are a table.
// may be nil if it cannot be determined (e.g., a nil slice or a zero length func isTableArray(arr reflect.Value) bool {
// slize). This function may also panic if it finds a type that cannot be if isNil(arr) || !arr.IsValid() || arr.Len() == 0 {
// expressed in TOML (such as nil elements, heterogeneous arrays or directly return false
// nested arrays of tables).
func tomlArrayType(rv reflect.Value) tomlType {
if isNil(rv) || !rv.IsValid() || rv.Len() == 0 {
return nil
} }
/// Don't allow nil. ret := true
rvlen := rv.Len() for i := 0; i < arr.Len(); i++ {
for i := 1; i < rvlen; i++ { tt := tomlTypeOfGo(eindirect(arr.Index(i)))
if tomlTypeOfGo(rv.Index(i)) == nil { // Don't allow nil.
if tt == nil {
encPanic(errArrayNilElement) encPanic(errArrayNilElement)
} }
}
firstType := tomlTypeOfGo(rv.Index(0)) if ret && !typeEqual(tomlHash, tt) {
if firstType == nil { ret = false
encPanic(errArrayNilElement) }
} }
return firstType return ret
} }
type tagOptions struct { type tagOptions struct {
@ -628,6 +652,8 @@ func isEmpty(rv reflect.Value) bool {
switch rv.Kind() { switch rv.Kind() {
case reflect.Array, reflect.Slice, reflect.Map, reflect.String: case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
return rv.Len() == 0 return rv.Len() == 0
case reflect.Struct:
return reflect.Zero(rv.Type()).Interface() == rv.Interface()
case reflect.Bool: case reflect.Bool:
return !rv.Bool() return !rv.Bool()
} }
@ -679,13 +705,25 @@ func encPanic(err error) {
panic(tomlEncodeError{err}) panic(tomlEncodeError{err})
} }
// Resolve any level of pointers to the actual value (e.g. **string → string).
func eindirect(v reflect.Value) reflect.Value { func eindirect(v reflect.Value) reflect.Value {
switch v.Kind() { if v.Kind() != reflect.Ptr && v.Kind() != reflect.Interface {
case reflect.Ptr, reflect.Interface: if isMarshaler(v) {
return eindirect(v.Elem()) return v
default: }
if v.CanAddr() { /// Special case for marshalers; see #358.
if pv := v.Addr(); isMarshaler(pv) {
return pv
}
}
return v return v
} }
if v.IsNil() {
return v
}
return eindirect(v.Elem())
} }
func isNil(rv reflect.Value) bool { func isNil(rv reflect.Value) bool {

View File

@ -128,9 +128,13 @@ func (pe ParseError) ErrorWithPosition() string {
func (pe ParseError) ErrorWithUsage() string { func (pe ParseError) ErrorWithUsage() string {
m := pe.ErrorWithPosition() m := pe.ErrorWithPosition()
if u, ok := pe.err.(interface{ Usage() string }); ok && u.Usage() != "" { if u, ok := pe.err.(interface{ Usage() string }); ok && u.Usage() != "" {
return m + "Error help:\n\n " + lines := strings.Split(strings.TrimSpace(u.Usage()), "\n")
strings.ReplaceAll(strings.TrimSpace(u.Usage()), "\n", "\n ") + for i := range lines {
"\n" if lines[i] != "" {
lines[i] = " " + lines[i]
}
}
return m + "Error help:\n\n" + strings.Join(lines, "\n") + "\n"
} }
return m return m
} }
@ -160,6 +164,11 @@ type (
errLexInvalidDate struct{ v string } errLexInvalidDate struct{ v string }
errLexInlineTableNL struct{} errLexInlineTableNL struct{}
errLexStringNL struct{} errLexStringNL struct{}
errParseRange struct {
i interface{} // int or float
size string // "int64", "uint16", etc.
}
errParseDuration struct{ d string }
) )
func (e errLexControl) Error() string { func (e errLexControl) Error() string {
@ -179,6 +188,10 @@ func (e errLexInlineTableNL) Error() string { return "newlines not allowed withi
func (e errLexInlineTableNL) Usage() string { return usageInlineNewline } func (e errLexInlineTableNL) Usage() string { return usageInlineNewline }
func (e errLexStringNL) Error() string { return "strings cannot contain newlines" } func (e errLexStringNL) Error() string { return "strings cannot contain newlines" }
func (e errLexStringNL) Usage() string { return usageStringNewline } func (e errLexStringNL) Usage() string { return usageStringNewline }
func (e errParseRange) Error() string { return fmt.Sprintf("%v is out of range for %s", e.i, e.size) }
func (e errParseRange) Usage() string { return usageIntOverflow }
func (e errParseDuration) Error() string { return fmt.Sprintf("invalid duration: %q", e.d) }
func (e errParseDuration) Usage() string { return usageDuration }
const usageEscape = ` const usageEscape = `
A '\' inside a "-delimited string is interpreted as an escape character. A '\' inside a "-delimited string is interpreted as an escape character.
@ -227,3 +240,37 @@ Instead use """ or ''' to split strings over multiple lines:
string = """Hello, string = """Hello,
world!""" world!"""
` `
const usageIntOverflow = `
This number is too large; this may be an error in the TOML, but it can also be a
bug in the program that uses too small of an integer.
The maximum and minimum values are:
size │ lowest │ highest
───────┼────────────────┼──────────
int8 │ -128 │ 127
int16 │ -32,768 │ 32,767
int32 │ -2,147,483,648 │ 2,147,483,647
int64 │ -9.2 × 10¹⁷ │ 9.2 × 10¹⁷
uint8 │ 0 │ 255
uint16 │ 0 │ 65535
uint32 │ 0 │ 4294967295
uint64 │ 0 │ 1.8 × 10¹⁸
int refers to int32 on 32-bit systems and int64 on 64-bit systems.
`
const usageDuration = `
A duration must be as "number<unit>", without any spaces. Valid units are:
ns nanoseconds (billionth of a second)
us, µs microseconds (millionth of a second)
ms milliseconds (thousands of a second)
s seconds
m minutes
h hours
You can combine multiple units; for example "5m10s" for 5 minutes and 10
seconds.
`

View File

@ -82,7 +82,7 @@ func (lx *lexer) nextItem() item {
return item return item
default: default:
lx.state = lx.state(lx) lx.state = lx.state(lx)
//fmt.Printf(" STATE %-24s current: %-10q stack: %s\n", lx.state, lx.current(), lx.stack) //fmt.Printf(" STATE %-24s current: %-10s stack: %s\n", lx.state, lx.current(), lx.stack)
} }
} }
} }
@ -716,7 +716,17 @@ func lexMultilineString(lx *lexer) stateFn {
if lx.peek() == '"' { if lx.peek() == '"' {
/// Check if we already lexed 5 's; if so we have 6 now, and /// Check if we already lexed 5 's; if so we have 6 now, and
/// that's just too many man! /// that's just too many man!
if strings.HasSuffix(lx.current(), `"""""`) { ///
/// Second check is for the edge case:
///
/// two quotes allowed.
/// vv
/// """lol \""""""
/// ^^ ^^^---- closing three
/// escaped
///
/// But ugly, but it works
if strings.HasSuffix(lx.current(), `"""""`) && !strings.HasSuffix(lx.current(), `\"""""`) {
return lx.errorf(`unexpected '""""""'`) return lx.errorf(`unexpected '""""""'`)
} }
lx.backup() lx.backup()
@ -807,8 +817,7 @@ func lexMultilineRawString(lx *lexer) stateFn {
// lexMultilineStringEscape consumes an escaped character. It assumes that the // lexMultilineStringEscape consumes an escaped character. It assumes that the
// preceding '\\' has already been consumed. // preceding '\\' has already been consumed.
func lexMultilineStringEscape(lx *lexer) stateFn { func lexMultilineStringEscape(lx *lexer) stateFn {
// Handle the special case first: if isNL(lx.next()) { /// \ escaping newline.
if isNL(lx.next()) {
return lexMultilineString return lexMultilineString
} }
lx.backup() lx.backup()

View File

@ -12,10 +12,11 @@ import (
type MetaData struct { type MetaData struct {
context Key // Used only during decoding. context Key // Used only during decoding.
keyInfo map[string]keyInfo
mapping map[string]interface{} mapping map[string]interface{}
types map[string]tomlType
keys []Key keys []Key
decoded map[string]struct{} decoded map[string]struct{}
data []byte // Input file; for errors.
} }
// IsDefined reports if the key exists in the TOML data. // IsDefined reports if the key exists in the TOML data.
@ -50,8 +51,8 @@ func (md *MetaData) IsDefined(key ...string) bool {
// Type will return the empty string if given an empty key or a key that does // Type will return the empty string if given an empty key or a key that does
// not exist. Keys are case sensitive. // not exist. Keys are case sensitive.
func (md *MetaData) Type(key ...string) string { func (md *MetaData) Type(key ...string) string {
if typ, ok := md.types[Key(key).String()]; ok { if ki, ok := md.keyInfo[Key(key).String()]; ok {
return typ.typeString() return ki.tomlType.typeString()
} }
return "" return ""
} }

View File

@ -16,12 +16,18 @@ type parser struct {
currentKey string // Base key name for everything except hashes. currentKey string // Base key name for everything except hashes.
pos Position // Current position in the TOML file. pos Position // Current position in the TOML file.
ordered []Key // List of keys in the order that they appear in the TOML data. ordered []Key // List of keys in the order that they appear in the TOML data.
keyInfo map[string]keyInfo // Map keyname → info about the TOML key.
mapping map[string]interface{} // Map keyname → key value. mapping map[string]interface{} // Map keyname → key value.
types map[string]tomlType // Map keyname → TOML type.
implicits map[string]struct{} // Record implicit keys (e.g. "key.group.names"). implicits map[string]struct{} // Record implicit keys (e.g. "key.group.names").
} }
type keyInfo struct {
pos Position
tomlType tomlType
}
func parse(data string) (p *parser, err error) { func parse(data string) (p *parser, err error) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -57,8 +63,8 @@ func parse(data string) (p *parser, err error) {
} }
p = &parser{ p = &parser{
keyInfo: make(map[string]keyInfo),
mapping: make(map[string]interface{}), mapping: make(map[string]interface{}),
types: make(map[string]tomlType),
lx: lex(data), lx: lex(data),
ordered: make([]Key, 0), ordered: make([]Key, 0),
implicits: make(map[string]struct{}), implicits: make(map[string]struct{}),
@ -74,6 +80,15 @@ func parse(data string) (p *parser, err error) {
return p, nil return p, nil
} }
func (p *parser) panicErr(it item, err error) {
panic(ParseError{
err: err,
Position: it.pos,
Line: it.pos.Len,
LastKey: p.current(),
})
}
func (p *parser) panicItemf(it item, format string, v ...interface{}) { func (p *parser) panicItemf(it item, format string, v ...interface{}) {
panic(ParseError{ panic(ParseError{
Message: fmt.Sprintf(format, v...), Message: fmt.Sprintf(format, v...),
@ -94,7 +109,7 @@ func (p *parser) panicf(format string, v ...interface{}) {
func (p *parser) next() item { func (p *parser) next() item {
it := p.lx.nextItem() it := p.lx.nextItem()
//fmt.Printf("ITEM %-18s line %-3d │ %q\n", it.typ, it.line, it.val) //fmt.Printf("ITEM %-18s line %-3d │ %q\n", it.typ, it.pos.Line, it.val)
if it.typ == itemError { if it.typ == itemError {
if it.err != nil { if it.err != nil {
panic(ParseError{ panic(ParseError{
@ -146,7 +161,7 @@ func (p *parser) topLevel(item item) {
p.assertEqual(itemTableEnd, name.typ) p.assertEqual(itemTableEnd, name.typ)
p.addContext(key, false) p.addContext(key, false)
p.setType("", tomlHash) p.setType("", tomlHash, item.pos)
p.ordered = append(p.ordered, key) p.ordered = append(p.ordered, key)
case itemArrayTableStart: // [[ .. ]] case itemArrayTableStart: // [[ .. ]]
name := p.nextPos() name := p.nextPos()
@ -158,7 +173,7 @@ func (p *parser) topLevel(item item) {
p.assertEqual(itemArrayTableEnd, name.typ) p.assertEqual(itemArrayTableEnd, name.typ)
p.addContext(key, true) p.addContext(key, true)
p.setType("", tomlArrayHash) p.setType("", tomlArrayHash, item.pos)
p.ordered = append(p.ordered, key) p.ordered = append(p.ordered, key)
case itemKeyStart: // key = .. case itemKeyStart: // key = ..
outerContext := p.context outerContext := p.context
@ -181,8 +196,9 @@ func (p *parser) topLevel(item item) {
} }
/// Set value. /// Set value.
val, typ := p.value(p.next(), false) vItem := p.next()
p.set(p.currentKey, val, typ) val, typ := p.value(vItem, false)
p.set(p.currentKey, val, typ, vItem.pos)
p.ordered = append(p.ordered, p.context.add(p.currentKey)) p.ordered = append(p.ordered, p.context.add(p.currentKey))
/// Remove the context we added (preserving any context from [tbl] lines). /// Remove the context we added (preserving any context from [tbl] lines).
@ -266,7 +282,7 @@ func (p *parser) valueInteger(it item) (interface{}, tomlType) {
// So mark the former as a bug but the latter as a legitimate user // So mark the former as a bug but the latter as a legitimate user
// error. // error.
if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange { if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange {
p.panicItemf(it, "Integer '%s' is out of the range of 64-bit signed integers.", it.val) p.panicErr(it, errParseRange{i: it.val, size: "int64"})
} else { } else {
p.bug("Expected integer value, but got '%s'.", it.val) p.bug("Expected integer value, but got '%s'.", it.val)
} }
@ -304,7 +320,7 @@ func (p *parser) valueFloat(it item) (interface{}, tomlType) {
num, err := strconv.ParseFloat(val, 64) num, err := strconv.ParseFloat(val, 64)
if err != nil { if err != nil {
if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange { if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange {
p.panicItemf(it, "Float '%s' is out of the range of 64-bit IEEE-754 floating-point numbers.", it.val) p.panicErr(it, errParseRange{i: it.val, size: "float64"})
} else { } else {
p.panicItemf(it, "Invalid float value: %q", it.val) p.panicItemf(it, "Invalid float value: %q", it.val)
} }
@ -343,9 +359,8 @@ func (p *parser) valueDatetime(it item) (interface{}, tomlType) {
} }
func (p *parser) valueArray(it item) (interface{}, tomlType) { func (p *parser) valueArray(it item) (interface{}, tomlType) {
p.setType(p.currentKey, tomlArray) p.setType(p.currentKey, tomlArray, it.pos)
// p.setType(p.currentKey, typ)
var ( var (
types []tomlType types []tomlType
@ -414,7 +429,7 @@ func (p *parser) valueInlineTable(it item, parentIsArray bool) (interface{}, tom
/// Set the value. /// Set the value.
val, typ := p.value(p.next(), false) val, typ := p.value(p.next(), false)
p.set(p.currentKey, val, typ) p.set(p.currentKey, val, typ, it.pos)
p.ordered = append(p.ordered, p.context.add(p.currentKey)) p.ordered = append(p.ordered, p.context.add(p.currentKey))
hash[p.currentKey] = val hash[p.currentKey] = val
@ -533,9 +548,10 @@ func (p *parser) addContext(key Key, array bool) {
} }
// set calls setValue and setType. // set calls setValue and setType.
func (p *parser) set(key string, val interface{}, typ tomlType) { func (p *parser) set(key string, val interface{}, typ tomlType, pos Position) {
p.setValue(key, val) p.setValue(key, val)
p.setType(key, typ) p.setType(key, typ, pos)
} }
// setValue sets the given key to the given value in the current context. // setValue sets the given key to the given value in the current context.
@ -599,7 +615,7 @@ func (p *parser) setValue(key string, value interface{}) {
// //
// Note that if `key` is empty, then the type given will be applied to the // Note that if `key` is empty, then the type given will be applied to the
// current context (which is either a table or an array of tables). // current context (which is either a table or an array of tables).
func (p *parser) setType(key string, typ tomlType) { func (p *parser) setType(key string, typ tomlType, pos Position) {
keyContext := make(Key, 0, len(p.context)+1) keyContext := make(Key, 0, len(p.context)+1)
keyContext = append(keyContext, p.context...) keyContext = append(keyContext, p.context...)
if len(key) > 0 { // allow type setting for hashes if len(key) > 0 { // allow type setting for hashes
@ -611,7 +627,7 @@ func (p *parser) setType(key string, typ tomlType) {
if len(keyContext) == 0 { if len(keyContext) == 0 {
keyContext = Key{""} keyContext = Key{""}
} }
p.types[keyContext.String()] = typ p.keyInfo[keyContext.String()] = keyInfo{tomlType: typ, pos: pos}
} }
// Implicit keys need to be created when tables are implied in "a.b.c.d = 1" and // Implicit keys need to be created when tables are implied in "a.b.c.d = 1" and
@ -619,7 +635,7 @@ func (p *parser) setType(key string, typ tomlType) {
func (p *parser) addImplicit(key Key) { p.implicits[key.String()] = struct{}{} } func (p *parser) addImplicit(key Key) { p.implicits[key.String()] = struct{}{} }
func (p *parser) removeImplicit(key Key) { delete(p.implicits, key.String()) } func (p *parser) removeImplicit(key Key) { delete(p.implicits, key.String()) }
func (p *parser) isImplicit(key Key) bool { _, ok := p.implicits[key.String()]; return ok } func (p *parser) isImplicit(key Key) bool { _, ok := p.implicits[key.String()]; return ok }
func (p *parser) isArray(key Key) bool { return p.types[key.String()] == tomlArray } func (p *parser) isArray(key Key) bool { return p.keyInfo[key.String()].tomlType == tomlArray }
func (p *parser) addImplicitContext(key Key) { func (p *parser) addImplicitContext(key Key) {
p.addImplicit(key) p.addImplicit(key)
p.addContext(key, false) p.addContext(key, false)
@ -710,10 +726,8 @@ func (p *parser) replaceEscapes(it item, str string) string {
switch s[r] { switch s[r] {
default: default:
p.bug("Expected valid escape code after \\, but got %q.", s[r]) p.bug("Expected valid escape code after \\, but got %q.", s[r])
return ""
case ' ', '\t': case ' ', '\t':
p.panicItemf(it, "invalid escape: '\\%c'", s[r]) p.panicItemf(it, "invalid escape: '\\%c'", s[r])
return ""
case 'b': case 'b':
replaced = append(replaced, rune(0x0008)) replaced = append(replaced, rune(0x0008))
r += 1 r += 1

2
vendor/modules.txt vendored
View File

@ -1,7 +1,7 @@
# github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 # github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1
github.com/Azure/go-ansiterm github.com/Azure/go-ansiterm
github.com/Azure/go-ansiterm/winterm github.com/Azure/go-ansiterm/winterm
# github.com/BurntSushi/toml v1.1.0 # github.com/BurntSushi/toml v1.2.0
## explicit ## explicit
github.com/BurntSushi/toml github.com/BurntSushi/toml
github.com/BurntSushi/toml/internal github.com/BurntSushi/toml/internal