mirror of
				https://github.com/NVIDIA/nvidia-container-toolkit
				synced 2025-06-26 18:18:24 +00:00 
			
		
		
		
	Merge branch 'upstream-fix-for-oci-1.0.0-rc2' into 'master'
Add support for parsing Linux Capabilities for older OCI specs See merge request nvidia/container-toolkit/container-toolkit!9
This commit is contained in:
		
						commit
						d112fbd98a
					
				
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							| @ -5,7 +5,7 @@ MKDIR    ?= mkdir | ||||
| DIST_DIR ?= $(CURDIR)/dist | ||||
| 
 | ||||
| LIB_NAME := nvidia-container-toolkit | ||||
| LIB_VERSION := 1.1.1 | ||||
| LIB_VERSION := 1.1.2 | ||||
| 
 | ||||
| GOLANG_VERSION := 1.14.2 | ||||
| GOLANG_PKG_PATH := github.com/NVIDIA/container-toolkit/pkg | ||||
|  | ||||
							
								
								
									
										6
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.mod
									
									
									
									
									
								
							| @ -2,4 +2,8 @@ module github.com/NVIDIA/container-toolkit | ||||
| 
 | ||||
| go 1.14 | ||||
| 
 | ||||
| require github.com/BurntSushi/toml v0.3.1 | ||||
| require ( | ||||
| 	github.com/BurntSushi/toml v0.3.1 | ||||
| 	github.com/stretchr/testify v1.6.0 | ||||
| 	golang.org/x/mod v0.3.0 | ||||
| ) | ||||
|  | ||||
							
								
								
									
										23
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								go.sum
									
									
									
									
									
								
							| @ -1,2 +1,25 @@ | ||||
| github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= | ||||
| github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= | ||||
| github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= | ||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||
| github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho= | ||||
| github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||||
| golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= | ||||
| golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= | ||||
| golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= | ||||
| golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||||
| golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= | ||||
| golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | ||||
| golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= | ||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
|  | ||||
| @ -1,3 +1,9 @@ | ||||
| nvidia-container-toolkit (1.1.2-1) UNRELEASED; urgency=medium | ||||
| 
 | ||||
|   * c32237f3 Add support for parsing Linux Capabilities for older OCI specs | ||||
| 
 | ||||
|  -- NVIDIA CORPORATION <cudatools@nvidia.com>  Wed, 03 Jun 2020 12:05:32 -0700 | ||||
| 
 | ||||
| nvidia-container-toolkit (1.1.1-1) UNRELEASED; urgency=medium | ||||
| 
 | ||||
|   * d202aded Update dependence to libnvidia-container 1.1.1 | ||||
|  | ||||
| @ -53,6 +53,9 @@ rm -f %{_bindir}/nvidia-container-runtime-hook | ||||
| /usr/share/containers/oci/hooks.d/oci-nvidia-hook.json | ||||
| 
 | ||||
| %changelog | ||||
| * Wed Jun 03 2020 NVIDIA CORPORATION <cudatools@nvidia.com> 1.1.2-1 | ||||
| - c32237f3 Add support for parsing Linux Capabilities for older OCI specs | ||||
| 
 | ||||
| * Tue May 19 2020 NVIDIA CORPORATION <cudatools@nvidia.com> 1.1.1-1 | ||||
| - d202aded Update dependence to libnvidia-container 1.1.1 | ||||
| 
 | ||||
|  | ||||
| @ -8,6 +8,8 @@ import ( | ||||
| 	"path" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"golang.org/x/mod/semver" | ||||
| ) | ||||
| 
 | ||||
| var envSwarmGPU *string | ||||
| @ -55,8 +57,8 @@ type Root struct { | ||||
| 
 | ||||
| // github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L30-L57
 | ||||
| type Process struct { | ||||
| 	Env          []string           `json:"env,omitempty"` | ||||
| 	Capabilities *LinuxCapabilities `json:"capabilities,omitempty" platform:"linux"` | ||||
| 	Env          []string         `json:"env,omitempty"` | ||||
| 	Capabilities *json.RawMessage `json:"capabilities,omitempty" platform:"linux"` | ||||
| } | ||||
| 
 | ||||
| // https://github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L61
 | ||||
| @ -71,6 +73,7 @@ type LinuxCapabilities struct { | ||||
| // We use pointers to structs, similarly to the latest version of runtime-spec:
 | ||||
| // https://github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L5-L28
 | ||||
| type Spec struct { | ||||
| 	Version *string  `json:"ociVersion"` | ||||
| 	Process *Process `json:"process,omitempty"` | ||||
| 	Root    *Root    `json:"root,omitempty"` | ||||
| } | ||||
| @ -133,6 +136,9 @@ func loadSpec(path string) (spec *Spec) { | ||||
| 	if err = json.NewDecoder(f).Decode(&spec); err != nil { | ||||
| 		log.Panicln("could not decode OCI spec:", err) | ||||
| 	} | ||||
| 	if spec.Version == nil { | ||||
| 		log.Panicln("Version is empty in OCI spec") | ||||
| 	} | ||||
| 	if spec.Process == nil { | ||||
| 		log.Panicln("Process is empty in OCI spec") | ||||
| 	} | ||||
| @ -142,29 +148,43 @@ func loadSpec(path string) (spec *Spec) { | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func isPrivileged(caps *LinuxCapabilities) bool { | ||||
| 	if caps == nil { | ||||
| func isPrivileged(s *Spec) bool { | ||||
| 	if s.Process.Capabilities == nil { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	hasCapSysAdmin := func(caps []string) bool { | ||||
| 		for _, c := range caps { | ||||
| 			if c == capSysAdmin { | ||||
| 				return true | ||||
| 			} | ||||
| 	var caps []string | ||||
| 	// If v1.1.0-rc1 <= OCI version < v1.0.0-rc5 parse s.Process.Capabilities as:
 | ||||
| 	// github.com/opencontainers/runtime-spec/blob/v1.0.0-rc1/specs-go/config.go#L30-L54
 | ||||
| 	rc1cmp := semver.Compare("v"+*s.Version, "v1.0.0-rc1") | ||||
| 	rc5cmp := semver.Compare("v"+*s.Version, "v1.0.0-rc5") | ||||
| 	if (rc1cmp == 1 || rc1cmp == 0) && (rc5cmp == -1) { | ||||
| 		err := json.Unmarshal(*s.Process.Capabilities, &caps) | ||||
| 		if err != nil { | ||||
| 			log.Panicln("could not decode Process.Capabilities in OCI spec:", err) | ||||
| 		} | ||||
| 		return false | ||||
| 		// Otherwise, parse s.Process.Capabilities as:
 | ||||
| 		// github.com/opencontainers/runtime-spec/blob/v1.0.0/specs-go/config.go#L30-L54
 | ||||
| 	} else { | ||||
| 		var lc LinuxCapabilities | ||||
| 		err := json.Unmarshal(*s.Process.Capabilities, &lc) | ||||
| 		if err != nil { | ||||
| 			log.Panicln("could not decode Process.Capabilities in OCI spec:", err) | ||||
| 		} | ||||
| 		// We only make sure that the bounding capabibility set has
 | ||||
| 		// CAP_SYS_ADMIN. This allows us to make sure that the container was
 | ||||
| 		// actually started as '--privileged', but also allow non-root users to
 | ||||
| 		// access the priviliged NVIDIA capabilities.
 | ||||
| 		caps = lc.Bounding | ||||
| 	} | ||||
| 
 | ||||
| 	// We only make sure that the bounding capabibility set has
 | ||||
| 	// CAP_SYS_ADMIN. This allows us to make sure that the container was
 | ||||
| 	// actually started as '--privileged', but also allow non-root users to
 | ||||
| 	// access the priviliged NVIDIA capabilities.
 | ||||
| 	if !hasCapSysAdmin(caps.Bounding) { | ||||
| 		return false | ||||
| 	for _, c := range caps { | ||||
| 		if c == capSysAdmin { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return true | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| func getDevices(env map[string]string) *string { | ||||
| @ -365,7 +385,7 @@ func getContainerConfig(hook HookConfig) (config containerConfig) { | ||||
| 	s := loadSpec(path.Join(b, "config.json")) | ||||
| 
 | ||||
| 	env := getEnvMap(s.Process.Env, hook.NvidiaContainerCLI) | ||||
| 	privileged := isPrivileged(s.Process.Capabilities) | ||||
| 	privileged := isPrivileged(s) | ||||
| 	envSwarmGPU = hook.SwarmResource | ||||
| 	return containerConfig{ | ||||
| 		Pid:    h.Pid, | ||||
|  | ||||
| @ -2,6 +2,7 @@ package main | ||||
| 
 | ||||
| import ( | ||||
| 	"testing" | ||||
| 	"encoding/json" | ||||
| ) | ||||
| 
 | ||||
| func TestParseCudaVersionValid(t *testing.T) { | ||||
| @ -58,3 +59,85 @@ func TestParseCudaVersionInvalid(t *testing.T) { | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestIsPrivileged(t *testing.T) { | ||||
| 	var tests = []struct { | ||||
| 		spec     string | ||||
| 		expected bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			` | ||||
| 			{ | ||||
| 				"ociVersion": "1.0.0", | ||||
| 				"process": { | ||||
| 					"capabilities": { | ||||
| 						"bounding": [ "CAP_SYS_ADMIN" ] | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			`, | ||||
| 			true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			` | ||||
| 			{ | ||||
| 				"ociVersion": "1.0.0", | ||||
| 				"process": { | ||||
| 					"capabilities": { | ||||
| 						"bounding": [ "CAP_SYS_OTHER" ] | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			`, | ||||
| 			false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			` | ||||
| 			{ | ||||
| 				"ociVersion": "1.0.0", | ||||
| 				"process": {} | ||||
| 			} | ||||
| 			`, | ||||
| 			false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			` | ||||
| 			{ | ||||
| 				"ociVersion": "1.0.0-rc2-dev", | ||||
| 				"process": { | ||||
| 					"capabilities": [ "CAP_SYS_ADMIN" ] | ||||
| 				} | ||||
| 			} | ||||
| 			`, | ||||
| 			true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			` | ||||
| 			{ | ||||
| 				"ociVersion": "1.0.0-rc2-dev", | ||||
| 				"process": { | ||||
| 					"capabilities": [ "CAP_SYS_OTHER" ] | ||||
| 				} | ||||
| 			} | ||||
| 			`, | ||||
| 			false, | ||||
| 		}, | ||||
| 		{ | ||||
| 			` | ||||
| 			{ | ||||
| 				"ociVersion": "1.0.0-rc2-dev", | ||||
| 				"process": {} | ||||
| 			} | ||||
| 			`, | ||||
| 			false, | ||||
| 		}, | ||||
| 	} | ||||
| 	for _, tc := range tests { | ||||
| 		var spec Spec | ||||
| 		_ = json.Unmarshal([]byte(tc.spec), &spec) | ||||
| 		privileged := isPrivileged(&spec) | ||||
| 		if privileged != tc.expected { | ||||
| 			t.Errorf("isPrivileged() returned unexpectred value (privileged: %v, tc.expected: %v)", privileged, tc.expected) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										5
									
								
								vendor/github.com/BurntSushi/toml/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								vendor/github.com/BurntSushi/toml/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,5 +0,0 @@ | ||||
| TAGS | ||||
| tags | ||||
| .*.swp | ||||
| tomlcheck/tomlcheck | ||||
| toml.test | ||||
							
								
								
									
										15
									
								
								vendor/github.com/BurntSushi/toml/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										15
									
								
								vendor/github.com/BurntSushi/toml/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,15 +0,0 @@ | ||||
| language: go | ||||
| go: | ||||
|   - 1.1 | ||||
|   - 1.2 | ||||
|   - 1.3 | ||||
|   - 1.4 | ||||
|   - 1.5 | ||||
|   - 1.6 | ||||
|   - tip | ||||
| install: | ||||
|   - go install ./... | ||||
|   - go get github.com/BurntSushi/toml-test | ||||
| script: | ||||
|   - export PATH="$PATH:$HOME/gopath/bin" | ||||
|   - make test | ||||
							
								
								
									
										3
									
								
								vendor/github.com/BurntSushi/toml/COMPATIBLE
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/BurntSushi/toml/COMPATIBLE
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,3 +0,0 @@ | ||||
| Compatible with TOML version | ||||
| [v0.4.0](https://github.com/toml-lang/toml/blob/v0.4.0/versions/en/toml-v0.4.0.md) | ||||
| 
 | ||||
							
								
								
									
										21
									
								
								vendor/github.com/BurntSushi/toml/COPYING
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/BurntSushi/toml/COPYING
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,21 +0,0 @@ | ||||
| The MIT License (MIT) | ||||
| 
 | ||||
| Copyright (c) 2013 TOML authors | ||||
| 
 | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
| 
 | ||||
| The above copyright notice and this permission notice shall be included in | ||||
| all copies or substantial portions of the Software. | ||||
| 
 | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| THE SOFTWARE. | ||||
							
								
								
									
										19
									
								
								vendor/github.com/BurntSushi/toml/Makefile
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								vendor/github.com/BurntSushi/toml/Makefile
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,19 +0,0 @@ | ||||
| install: | ||||
| 	go install ./... | ||||
| 
 | ||||
| test: install | ||||
| 	go test -v | ||||
| 	toml-test toml-test-decoder | ||||
| 	toml-test -encoder toml-test-encoder | ||||
| 
 | ||||
| fmt: | ||||
| 	gofmt -w *.go */*.go | ||||
| 	colcheck *.go */*.go | ||||
| 
 | ||||
| tags: | ||||
| 	find ./ -name '*.go' -print0 | xargs -0 gotags > TAGS | ||||
| 
 | ||||
| push: | ||||
| 	git push origin master | ||||
| 	git push github master | ||||
| 
 | ||||
							
								
								
									
										218
									
								
								vendor/github.com/BurntSushi/toml/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										218
									
								
								vendor/github.com/BurntSushi/toml/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,218 +0,0 @@ | ||||
| ## 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](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md) | ||||
| 
 | ||||
| Documentation: https://godoc.org/github.com/BurntSushi/toml | ||||
| 
 | ||||
| Installation: | ||||
| 
 | ||||
| ```bash | ||||
| go get github.com/BurntSushi/toml | ||||
| ``` | ||||
| 
 | ||||
| Try the toml validator: | ||||
| 
 | ||||
| ```bash | ||||
| go get github.com/BurntSushi/toml/cmd/tomlv | ||||
| tomlv some-toml-file.toml | ||||
| ``` | ||||
| 
 | ||||
| [](https://travis-ci.org/BurntSushi/toml) [](https://godoc.org/github.com/BurntSushi/toml) | ||||
| 
 | ||||
| ### Testing | ||||
| 
 | ||||
| This package passes all tests in | ||||
| [toml-test](https://github.com/BurntSushi/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: | ||||
| 
 | ||||
| ```toml | ||||
| 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: | ||||
| 
 | ||||
| ```go | ||||
| type Config struct { | ||||
|   Age int | ||||
|   Cats []string | ||||
|   Pi float64 | ||||
|   Perfection []int | ||||
|   DOB time.Time // requires `import time` | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| And then decoded with: | ||||
| 
 | ||||
| ```go | ||||
| 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: | ||||
| 
 | ||||
| ```toml | ||||
| some_key_NAME = "wat" | ||||
| ``` | ||||
| 
 | ||||
| ```go | ||||
| 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: | ||||
| 
 | ||||
| ```toml | ||||
| [[song]] | ||||
| name = "Thunder Road" | ||||
| duration = "4m49s" | ||||
| 
 | ||||
| [[song]] | ||||
| name = "Stairway to Heaven" | ||||
| duration = "8m03s" | ||||
| ``` | ||||
| 
 | ||||
| Which can be decoded with: | ||||
| 
 | ||||
| ```go | ||||
| 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: | ||||
| 
 | ||||
| ```go | ||||
| 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: | ||||
| 
 | ||||
| ```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 `_examples/example.{go,toml}`. | ||||
							
								
								
									
										509
									
								
								vendor/github.com/BurntSushi/toml/decode.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										509
									
								
								vendor/github.com/BurntSushi/toml/decode.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,509 +0,0 @@ | ||||
| package toml | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"math" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| func e(format string, args ...interface{}) error { | ||||
| 	return fmt.Errorf("toml: "+format, args...) | ||||
| } | ||||
| 
 | ||||
| // Unmarshaler is the interface implemented by objects that can unmarshal a
 | ||||
| // TOML description of themselves.
 | ||||
| type Unmarshaler interface { | ||||
| 	UnmarshalTOML(interface{}) error | ||||
| } | ||||
| 
 | ||||
| // Unmarshal decodes the contents of `p` in TOML format into a pointer `v`.
 | ||||
| func Unmarshal(p []byte, v interface{}) error { | ||||
| 	_, err := Decode(string(p), v) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // Primitive is a TOML value that hasn't been decoded into a Go value.
 | ||||
| // When using the various `Decode*` functions, the type `Primitive` may
 | ||||
| // be given to any value, and its decoding will be delayed.
 | ||||
| //
 | ||||
| // A `Primitive` value can be decoded using the `PrimitiveDecode` function.
 | ||||
| //
 | ||||
| // The underlying representation of a `Primitive` value is subject to change.
 | ||||
| // Do not rely on it.
 | ||||
| //
 | ||||
| // N.B. Primitive values are still parsed, so using them will only avoid
 | ||||
| // the overhead of reflection. They can be useful when you don't know the
 | ||||
| // exact type of TOML data until run time.
 | ||||
| type Primitive struct { | ||||
| 	undecoded interface{} | ||||
| 	context   Key | ||||
| } | ||||
| 
 | ||||
| // DEPRECATED!
 | ||||
| //
 | ||||
| // Use MetaData.PrimitiveDecode instead.
 | ||||
| func PrimitiveDecode(primValue Primitive, v interface{}) error { | ||||
| 	md := MetaData{decoded: make(map[string]bool)} | ||||
| 	return md.unify(primValue.undecoded, rvalue(v)) | ||||
| } | ||||
| 
 | ||||
| // PrimitiveDecode is just like the other `Decode*` functions, except it
 | ||||
| // decodes a TOML value that has already been parsed. Valid primitive values
 | ||||
| // can *only* be obtained from values filled by the decoder functions,
 | ||||
| // including this method. (i.e., `v` may contain more `Primitive`
 | ||||
| // values.)
 | ||||
| //
 | ||||
| // Meta data for primitive values is included in the meta data returned by
 | ||||
| // the `Decode*` functions with one exception: keys returned by the Undecoded
 | ||||
| // method will only reflect keys that were decoded. Namely, any keys hidden
 | ||||
| // behind a Primitive will be considered undecoded. Executing this method will
 | ||||
| // update the undecoded keys in the meta data. (See the example.)
 | ||||
| func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error { | ||||
| 	md.context = primValue.context | ||||
| 	defer func() { md.context = nil }() | ||||
| 	return md.unify(primValue.undecoded, rvalue(v)) | ||||
| } | ||||
| 
 | ||||
| // Decode will decode the contents of `data` in TOML format into a pointer
 | ||||
| // `v`.
 | ||||
| //
 | ||||
| // TOML hashes correspond to Go structs or maps. (Dealer's choice. They can be
 | ||||
| // used interchangeably.)
 | ||||
| //
 | ||||
| // TOML arrays of tables correspond to either a slice of structs or a slice
 | ||||
| // of maps.
 | ||||
| //
 | ||||
| // TOML datetimes correspond to Go `time.Time` values.
 | ||||
| //
 | ||||
| // All other TOML types (float, string, int, bool and array) correspond
 | ||||
| // to the obvious Go types.
 | ||||
| //
 | ||||
| // An exception to the above rules is if a type implements the
 | ||||
| // encoding.TextUnmarshaler interface. In this case, any primitive TOML value
 | ||||
| // (floats, strings, integers, booleans and datetimes) will be converted to
 | ||||
| // a byte string and given to the value's UnmarshalText method. See the
 | ||||
| // Unmarshaler example for a demonstration with time duration strings.
 | ||||
| //
 | ||||
| // Key mapping
 | ||||
| //
 | ||||
| // TOML keys can map to either keys in a Go map or field names in a Go
 | ||||
| // struct. The special `toml` struct tag may be used to map TOML keys to
 | ||||
| // struct fields that don't match the key name exactly. (See the example.)
 | ||||
| // A case insensitive match to struct names will be tried if an exact match
 | ||||
| // can't be found.
 | ||||
| //
 | ||||
| // The mapping between TOML values and Go values is loose. That is, there
 | ||||
| // may exist TOML values that cannot be placed into your representation, and
 | ||||
| // there may be parts of your representation that do not correspond to
 | ||||
| // TOML values. This loose mapping can be made stricter by using the IsDefined
 | ||||
| // and/or Undecoded methods on the MetaData returned.
 | ||||
| //
 | ||||
| // This decoder will not handle cyclic types. If a cyclic type is passed,
 | ||||
| // `Decode` will not terminate.
 | ||||
| func Decode(data string, v interface{}) (MetaData, error) { | ||||
| 	rv := reflect.ValueOf(v) | ||||
| 	if rv.Kind() != reflect.Ptr { | ||||
| 		return MetaData{}, e("Decode of non-pointer %s", reflect.TypeOf(v)) | ||||
| 	} | ||||
| 	if rv.IsNil() { | ||||
| 		return MetaData{}, e("Decode of nil %s", reflect.TypeOf(v)) | ||||
| 	} | ||||
| 	p, err := parse(data) | ||||
| 	if err != nil { | ||||
| 		return MetaData{}, err | ||||
| 	} | ||||
| 	md := MetaData{ | ||||
| 		p.mapping, p.types, p.ordered, | ||||
| 		make(map[string]bool, len(p.ordered)), nil, | ||||
| 	} | ||||
| 	return md, md.unify(p.mapping, indirect(rv)) | ||||
| } | ||||
| 
 | ||||
| // DecodeFile is just like Decode, except it will automatically read the
 | ||||
| // contents of the file at `fpath` and decode it for you.
 | ||||
| func DecodeFile(fpath string, v interface{}) (MetaData, error) { | ||||
| 	bs, err := ioutil.ReadFile(fpath) | ||||
| 	if err != nil { | ||||
| 		return MetaData{}, err | ||||
| 	} | ||||
| 	return Decode(string(bs), v) | ||||
| } | ||||
| 
 | ||||
| // DecodeReader is just like Decode, except it will consume all bytes
 | ||||
| // from the reader and decode it for you.
 | ||||
| func DecodeReader(r io.Reader, v interface{}) (MetaData, error) { | ||||
| 	bs, err := ioutil.ReadAll(r) | ||||
| 	if err != nil { | ||||
| 		return MetaData{}, err | ||||
| 	} | ||||
| 	return Decode(string(bs), v) | ||||
| } | ||||
| 
 | ||||
| // unify performs a sort of type unification based on the structure of `rv`,
 | ||||
| // which is the client representation.
 | ||||
| //
 | ||||
| // Any type mismatch produces an error. Finding a type that we don't know
 | ||||
| // how to handle produces an unsupported type error.
 | ||||
| func (md *MetaData) unify(data interface{}, rv reflect.Value) error { | ||||
| 
 | ||||
| 	// Special case. Look for a `Primitive` value.
 | ||||
| 	if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() { | ||||
| 		// Save the undecoded data and the key context into the primitive
 | ||||
| 		// value.
 | ||||
| 		context := make(Key, len(md.context)) | ||||
| 		copy(context, md.context) | ||||
| 		rv.Set(reflect.ValueOf(Primitive{ | ||||
| 			undecoded: data, | ||||
| 			context:   context, | ||||
| 		})) | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	// Special case. Unmarshaler Interface support.
 | ||||
| 	if rv.CanAddr() { | ||||
| 		if v, ok := rv.Addr().Interface().(Unmarshaler); ok { | ||||
| 			return v.UnmarshalTOML(data) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	// Special case. Handle time.Time values specifically.
 | ||||
| 	// TODO: Remove this code when we decide to drop support for Go 1.1.
 | ||||
| 	// This isn't necessary in Go 1.2 because time.Time satisfies the encoding
 | ||||
| 	// interfaces.
 | ||||
| 	if rv.Type().AssignableTo(rvalue(time.Time{}).Type()) { | ||||
| 		return md.unifyDatetime(data, rv) | ||||
| 	} | ||||
| 
 | ||||
| 	// Special case. Look for a value satisfying the TextUnmarshaler interface.
 | ||||
| 	if v, ok := rv.Interface().(TextUnmarshaler); ok { | ||||
| 		return md.unifyText(data, v) | ||||
| 	} | ||||
| 	// BUG(burntsushi)
 | ||||
| 	// The behavior here is incorrect whenever a Go type satisfies the
 | ||||
| 	// encoding.TextUnmarshaler interface but also corresponds to a TOML
 | ||||
| 	// hash or array. In particular, the unmarshaler should only be applied
 | ||||
| 	// to primitive TOML values. But at this point, it will be applied to
 | ||||
| 	// all kinds of values and produce an incorrect error whenever those values
 | ||||
| 	// are hashes or arrays (including arrays of tables).
 | ||||
| 
 | ||||
| 	k := rv.Kind() | ||||
| 
 | ||||
| 	// laziness
 | ||||
| 	if k >= reflect.Int && k <= reflect.Uint64 { | ||||
| 		return md.unifyInt(data, rv) | ||||
| 	} | ||||
| 	switch k { | ||||
| 	case reflect.Ptr: | ||||
| 		elem := reflect.New(rv.Type().Elem()) | ||||
| 		err := md.unify(data, reflect.Indirect(elem)) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		rv.Set(elem) | ||||
| 		return nil | ||||
| 	case reflect.Struct: | ||||
| 		return md.unifyStruct(data, rv) | ||||
| 	case reflect.Map: | ||||
| 		return md.unifyMap(data, rv) | ||||
| 	case reflect.Array: | ||||
| 		return md.unifyArray(data, rv) | ||||
| 	case reflect.Slice: | ||||
| 		return md.unifySlice(data, rv) | ||||
| 	case reflect.String: | ||||
| 		return md.unifyString(data, rv) | ||||
| 	case reflect.Bool: | ||||
| 		return md.unifyBool(data, rv) | ||||
| 	case reflect.Interface: | ||||
| 		// we only support empty interfaces.
 | ||||
| 		if rv.NumMethod() > 0 { | ||||
| 			return e("unsupported type %s", rv.Type()) | ||||
| 		} | ||||
| 		return md.unifyAnything(data, rv) | ||||
| 	case reflect.Float32: | ||||
| 		fallthrough | ||||
| 	case reflect.Float64: | ||||
| 		return md.unifyFloat64(data, rv) | ||||
| 	} | ||||
| 	return e("unsupported type %s", rv.Kind()) | ||||
| } | ||||
| 
 | ||||
| func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error { | ||||
| 	tmap, ok := mapping.(map[string]interface{}) | ||||
| 	if !ok { | ||||
| 		if mapping == nil { | ||||
| 			return nil | ||||
| 		} | ||||
| 		return e("type mismatch for %s: expected table but found %T", | ||||
| 			rv.Type().String(), mapping) | ||||
| 	} | ||||
| 
 | ||||
| 	for key, datum := range tmap { | ||||
| 		var f *field | ||||
| 		fields := cachedTypeFields(rv.Type()) | ||||
| 		for i := range fields { | ||||
| 			ff := &fields[i] | ||||
| 			if ff.name == key { | ||||
| 				f = ff | ||||
| 				break | ||||
| 			} | ||||
| 			if f == nil && strings.EqualFold(ff.name, key) { | ||||
| 				f = ff | ||||
| 			} | ||||
| 		} | ||||
| 		if f != nil { | ||||
| 			subv := rv | ||||
| 			for _, i := range f.index { | ||||
| 				subv = indirect(subv.Field(i)) | ||||
| 			} | ||||
| 			if isUnifiable(subv) { | ||||
| 				md.decoded[md.context.add(key).String()] = true | ||||
| 				md.context = append(md.context, key) | ||||
| 				if err := md.unify(datum, subv); err != nil { | ||||
| 					return err | ||||
| 				} | ||||
| 				md.context = md.context[0 : len(md.context)-1] | ||||
| 			} else if f.name != "" { | ||||
| 				// Bad user! No soup for you!
 | ||||
| 				return e("cannot write unexported field %s.%s", | ||||
| 					rv.Type().String(), f.name) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error { | ||||
| 	tmap, ok := mapping.(map[string]interface{}) | ||||
| 	if !ok { | ||||
| 		if tmap == nil { | ||||
| 			return nil | ||||
| 		} | ||||
| 		return badtype("map", mapping) | ||||
| 	} | ||||
| 	if rv.IsNil() { | ||||
| 		rv.Set(reflect.MakeMap(rv.Type())) | ||||
| 	} | ||||
| 	for k, v := range tmap { | ||||
| 		md.decoded[md.context.add(k).String()] = true | ||||
| 		md.context = append(md.context, k) | ||||
| 
 | ||||
| 		rvkey := indirect(reflect.New(rv.Type().Key())) | ||||
| 		rvval := reflect.Indirect(reflect.New(rv.Type().Elem())) | ||||
| 		if err := md.unify(v, rvval); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		md.context = md.context[0 : len(md.context)-1] | ||||
| 
 | ||||
| 		rvkey.SetString(k) | ||||
| 		rv.SetMapIndex(rvkey, rvval) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error { | ||||
| 	datav := reflect.ValueOf(data) | ||||
| 	if datav.Kind() != reflect.Slice { | ||||
| 		if !datav.IsValid() { | ||||
| 			return nil | ||||
| 		} | ||||
| 		return badtype("slice", data) | ||||
| 	} | ||||
| 	sliceLen := datav.Len() | ||||
| 	if sliceLen != rv.Len() { | ||||
| 		return e("expected array length %d; got TOML array of length %d", | ||||
| 			rv.Len(), sliceLen) | ||||
| 	} | ||||
| 	return md.unifySliceArray(datav, rv) | ||||
| } | ||||
| 
 | ||||
| func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error { | ||||
| 	datav := reflect.ValueOf(data) | ||||
| 	if datav.Kind() != reflect.Slice { | ||||
| 		if !datav.IsValid() { | ||||
| 			return nil | ||||
| 		} | ||||
| 		return badtype("slice", data) | ||||
| 	} | ||||
| 	n := datav.Len() | ||||
| 	if rv.IsNil() || rv.Cap() < n { | ||||
| 		rv.Set(reflect.MakeSlice(rv.Type(), n, n)) | ||||
| 	} | ||||
| 	rv.SetLen(n) | ||||
| 	return md.unifySliceArray(datav, rv) | ||||
| } | ||||
| 
 | ||||
| func (md *MetaData) unifySliceArray(data, rv reflect.Value) error { | ||||
| 	sliceLen := data.Len() | ||||
| 	for i := 0; i < sliceLen; i++ { | ||||
| 		v := data.Index(i).Interface() | ||||
| 		sliceval := indirect(rv.Index(i)) | ||||
| 		if err := md.unify(v, sliceval); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (md *MetaData) unifyDatetime(data interface{}, rv reflect.Value) error { | ||||
| 	if _, ok := data.(time.Time); ok { | ||||
| 		rv.Set(reflect.ValueOf(data)) | ||||
| 		return nil | ||||
| 	} | ||||
| 	return badtype("time.Time", data) | ||||
| } | ||||
| 
 | ||||
| func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error { | ||||
| 	if s, ok := data.(string); ok { | ||||
| 		rv.SetString(s) | ||||
| 		return nil | ||||
| 	} | ||||
| 	return badtype("string", data) | ||||
| } | ||||
| 
 | ||||
| func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error { | ||||
| 	if num, ok := data.(float64); ok { | ||||
| 		switch rv.Kind() { | ||||
| 		case reflect.Float32: | ||||
| 			fallthrough | ||||
| 		case reflect.Float64: | ||||
| 			rv.SetFloat(num) | ||||
| 		default: | ||||
| 			panic("bug") | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| 	return badtype("float", data) | ||||
| } | ||||
| 
 | ||||
| func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error { | ||||
| 	if num, ok := data.(int64); ok { | ||||
| 		if rv.Kind() >= reflect.Int && rv.Kind() <= reflect.Int64 { | ||||
| 			switch rv.Kind() { | ||||
| 			case reflect.Int, reflect.Int64: | ||||
| 				// No bounds checking necessary.
 | ||||
| 			case reflect.Int8: | ||||
| 				if num < math.MinInt8 || num > math.MaxInt8 { | ||||
| 					return e("value %d is out of range for int8", num) | ||||
| 				} | ||||
| 			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) | ||||
| 		} else if rv.Kind() >= reflect.Uint && rv.Kind() <= reflect.Uint64 { | ||||
| 			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 badtype("integer", data) | ||||
| } | ||||
| 
 | ||||
| func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error { | ||||
| 	if b, ok := data.(bool); ok { | ||||
| 		rv.SetBool(b) | ||||
| 		return nil | ||||
| 	} | ||||
| 	return badtype("boolean", data) | ||||
| } | ||||
| 
 | ||||
| func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error { | ||||
| 	rv.Set(reflect.ValueOf(data)) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error { | ||||
| 	var s string | ||||
| 	switch sdata := data.(type) { | ||||
| 	case TextMarshaler: | ||||
| 		text, err := sdata.MarshalText() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		s = string(text) | ||||
| 	case fmt.Stringer: | ||||
| 		s = sdata.String() | ||||
| 	case string: | ||||
| 		s = sdata | ||||
| 	case bool: | ||||
| 		s = fmt.Sprintf("%v", sdata) | ||||
| 	case int64: | ||||
| 		s = fmt.Sprintf("%d", sdata) | ||||
| 	case float64: | ||||
| 		s = fmt.Sprintf("%f", sdata) | ||||
| 	default: | ||||
| 		return badtype("primitive (string-like)", data) | ||||
| 	} | ||||
| 	if err := v.UnmarshalText([]byte(s)); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // rvalue returns a reflect.Value of `v`. All pointers are resolved.
 | ||||
| func rvalue(v interface{}) reflect.Value { | ||||
| 	return indirect(reflect.ValueOf(v)) | ||||
| } | ||||
| 
 | ||||
| // indirect returns the value pointed to by a pointer.
 | ||||
| // Pointers are followed until the value is not a pointer.
 | ||||
| // New values are allocated for each nil pointer.
 | ||||
| //
 | ||||
| // An exception to this rule is if the value satisfies an interface of
 | ||||
| // interest to us (like encoding.TextUnmarshaler).
 | ||||
| func indirect(v reflect.Value) reflect.Value { | ||||
| 	if v.Kind() != reflect.Ptr { | ||||
| 		if v.CanSet() { | ||||
| 			pv := v.Addr() | ||||
| 			if _, ok := pv.Interface().(TextUnmarshaler); ok { | ||||
| 				return pv | ||||
| 			} | ||||
| 		} | ||||
| 		return v | ||||
| 	} | ||||
| 	if v.IsNil() { | ||||
| 		v.Set(reflect.New(v.Type().Elem())) | ||||
| 	} | ||||
| 	return indirect(reflect.Indirect(v)) | ||||
| } | ||||
| 
 | ||||
| func isUnifiable(rv reflect.Value) bool { | ||||
| 	if rv.CanSet() { | ||||
| 		return true | ||||
| 	} | ||||
| 	if _, ok := rv.Interface().(TextUnmarshaler); ok { | ||||
| 		return true | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| func badtype(expected string, data interface{}) error { | ||||
| 	return e("cannot load TOML value of type %T into a Go %s", data, expected) | ||||
| } | ||||
							
								
								
									
										121
									
								
								vendor/github.com/BurntSushi/toml/decode_meta.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										121
									
								
								vendor/github.com/BurntSushi/toml/decode_meta.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,121 +0,0 @@ | ||||
| package toml | ||||
| 
 | ||||
| import "strings" | ||||
| 
 | ||||
| // MetaData allows access to meta information about TOML data that may not
 | ||||
| // be inferrable via reflection. In particular, whether a key has been defined
 | ||||
| // and the TOML type of a key.
 | ||||
| type MetaData struct { | ||||
| 	mapping map[string]interface{} | ||||
| 	types   map[string]tomlType | ||||
| 	keys    []Key | ||||
| 	decoded map[string]bool | ||||
| 	context Key // Used only during decoding.
 | ||||
| } | ||||
| 
 | ||||
| // IsDefined returns true if the key given exists in the TOML data. The key
 | ||||
| // should be specified hierarchially. e.g.,
 | ||||
| //
 | ||||
| //	// access the TOML key 'a.b.c'
 | ||||
| //	IsDefined("a", "b", "c")
 | ||||
| //
 | ||||
| // IsDefined will return false if an empty key given. Keys are case sensitive.
 | ||||
| func (md *MetaData) IsDefined(key ...string) bool { | ||||
| 	if len(key) == 0 { | ||||
| 		return false | ||||
| 	} | ||||
| 
 | ||||
| 	var hash map[string]interface{} | ||||
| 	var ok bool | ||||
| 	var hashOrVal interface{} = md.mapping | ||||
| 	for _, k := range key { | ||||
| 		if hash, ok = hashOrVal.(map[string]interface{}); !ok { | ||||
| 			return false | ||||
| 		} | ||||
| 		if hashOrVal, ok = hash[k]; !ok { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
| 
 | ||||
| // Type returns a string representation of the type of the key specified.
 | ||||
| //
 | ||||
| // Type will return the empty string if given an empty key or a key that
 | ||||
| // does not exist. Keys are case sensitive.
 | ||||
| func (md *MetaData) Type(key ...string) string { | ||||
| 	fullkey := strings.Join(key, ".") | ||||
| 	if typ, ok := md.types[fullkey]; ok { | ||||
| 		return typ.typeString() | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
| 
 | ||||
| // Key is the type of any TOML key, including key groups. Use (MetaData).Keys
 | ||||
| // to get values of this type.
 | ||||
| type Key []string | ||||
| 
 | ||||
| func (k Key) String() string { | ||||
| 	return strings.Join(k, ".") | ||||
| } | ||||
| 
 | ||||
| func (k Key) maybeQuotedAll() string { | ||||
| 	var ss []string | ||||
| 	for i := range k { | ||||
| 		ss = append(ss, k.maybeQuoted(i)) | ||||
| 	} | ||||
| 	return strings.Join(ss, ".") | ||||
| } | ||||
| 
 | ||||
| func (k Key) maybeQuoted(i int) string { | ||||
| 	quote := false | ||||
| 	for _, c := range k[i] { | ||||
| 		if !isBareKeyChar(c) { | ||||
| 			quote = true | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	if quote { | ||||
| 		return "\"" + strings.Replace(k[i], "\"", "\\\"", -1) + "\"" | ||||
| 	} | ||||
| 	return k[i] | ||||
| } | ||||
| 
 | ||||
| func (k Key) add(piece string) Key { | ||||
| 	newKey := make(Key, len(k)+1) | ||||
| 	copy(newKey, k) | ||||
| 	newKey[len(k)] = piece | ||||
| 	return newKey | ||||
| } | ||||
| 
 | ||||
| // Keys returns a slice of every key in the TOML data, including key groups.
 | ||||
| // Each key is itself a slice, where the first element is the top of the
 | ||||
| // hierarchy and the last is the most specific.
 | ||||
| //
 | ||||
| // The list will have the same order as the keys appeared in the TOML data.
 | ||||
| //
 | ||||
| // All keys returned are non-empty.
 | ||||
| func (md *MetaData) Keys() []Key { | ||||
| 	return md.keys | ||||
| } | ||||
| 
 | ||||
| // Undecoded returns all keys that have not been decoded in the order in which
 | ||||
| // they appear in the original TOML document.
 | ||||
| //
 | ||||
| // This includes keys that haven't been decoded because of a Primitive value.
 | ||||
| // Once the Primitive value is decoded, the keys will be considered decoded.
 | ||||
| //
 | ||||
| // Also note that decoding into an empty interface will result in no decoding,
 | ||||
| // and so no keys will be considered decoded.
 | ||||
| //
 | ||||
| // In this sense, the Undecoded keys correspond to keys in the TOML document
 | ||||
| // that do not have a concrete type in your representation.
 | ||||
| func (md *MetaData) Undecoded() []Key { | ||||
| 	undecoded := make([]Key, 0, len(md.keys)) | ||||
| 	for _, key := range md.keys { | ||||
| 		if !md.decoded[key.String()] { | ||||
| 			undecoded = append(undecoded, key) | ||||
| 		} | ||||
| 	} | ||||
| 	return undecoded | ||||
| } | ||||
							
								
								
									
										27
									
								
								vendor/github.com/BurntSushi/toml/doc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										27
									
								
								vendor/github.com/BurntSushi/toml/doc.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,27 +0,0 @@ | ||||
| /* | ||||
| 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. | ||||
| */ | ||||
| package toml | ||||
							
								
								
									
										568
									
								
								vendor/github.com/BurntSushi/toml/encode.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										568
									
								
								vendor/github.com/BurntSushi/toml/encode.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,568 +0,0 @@ | ||||
| package toml | ||||
| 
 | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"reflect" | ||||
| 	"sort" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
| 
 | ||||
| type tomlEncodeError struct{ error } | ||||
| 
 | ||||
| var ( | ||||
| 	errArrayMixedElementTypes = errors.New( | ||||
| 		"toml: cannot encode array with mixed element types") | ||||
| 	errArrayNilElement = errors.New( | ||||
| 		"toml: cannot encode array with nil element") | ||||
| 	errNonString = errors.New( | ||||
| 		"toml: cannot encode a map with non-string key type") | ||||
| 	errAnonNonStruct = errors.New( | ||||
| 		"toml: cannot encode an anonymous field that is not a struct") | ||||
| 	errArrayNoTable = errors.New( | ||||
| 		"toml: TOML array element cannot contain a table") | ||||
| 	errNoKey = errors.New( | ||||
| 		"toml: top-level values must be Go maps or structs") | ||||
| 	errAnything = errors.New("") // used in testing
 | ||||
| ) | ||||
| 
 | ||||
| var quotedReplacer = strings.NewReplacer( | ||||
| 	"\t", "\\t", | ||||
| 	"\n", "\\n", | ||||
| 	"\r", "\\r", | ||||
| 	"\"", "\\\"", | ||||
| 	"\\", "\\\\", | ||||
| ) | ||||
| 
 | ||||
| // Encoder controls the encoding of Go values to a TOML document to some
 | ||||
| // io.Writer.
 | ||||
| //
 | ||||
| // The indentation level can be controlled with the Indent field.
 | ||||
| type Encoder struct { | ||||
| 	// A single indentation level. By default it is two spaces.
 | ||||
| 	Indent string | ||||
| 
 | ||||
| 	// hasWritten is whether we have written any output to w yet.
 | ||||
| 	hasWritten bool | ||||
| 	w          *bufio.Writer | ||||
| } | ||||
| 
 | ||||
| // NewEncoder returns a TOML encoder that encodes Go values to the io.Writer
 | ||||
| // given. By default, a single indentation level is 2 spaces.
 | ||||
| func NewEncoder(w io.Writer) *Encoder { | ||||
| 	return &Encoder{ | ||||
| 		w:      bufio.NewWriter(w), | ||||
| 		Indent: "  ", | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Encode writes a TOML representation of the Go value to the underlying
 | ||||
| // io.Writer. If the value given cannot be encoded to a valid TOML document,
 | ||||
| // then an error is returned.
 | ||||
| //
 | ||||
| // The mapping between Go values and TOML values should be precisely the same
 | ||||
| // as for the Decode* functions. Similarly, the TextMarshaler interface is
 | ||||
| // supported by encoding the resulting bytes as strings. (If you want to write
 | ||||
| // arbitrary binary data then you will need to use something like base64 since
 | ||||
| // TOML does not have any binary types.)
 | ||||
| //
 | ||||
| // When encoding TOML hashes (i.e., Go maps or structs), keys without any
 | ||||
| // sub-hashes are encoded first.
 | ||||
| //
 | ||||
| // If a Go map is encoded, then its keys are sorted alphabetically for
 | ||||
| // deterministic output. More control over this behavior may be provided if
 | ||||
| // there is demand for it.
 | ||||
| //
 | ||||
| // Encoding Go values without a corresponding TOML representation---like map
 | ||||
| // types with non-string keys---will cause an error to be returned. Similarly
 | ||||
| // for mixed arrays/slices, arrays/slices with nil elements, embedded
 | ||||
| // non-struct types and nested slices containing maps or structs.
 | ||||
| // (e.g., [][]map[string]string is not allowed but []map[string]string is OK
 | ||||
| // and so is []map[string][]string.)
 | ||||
| func (enc *Encoder) Encode(v interface{}) error { | ||||
| 	rv := eindirect(reflect.ValueOf(v)) | ||||
| 	if err := enc.safeEncode(Key([]string{}), rv); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return enc.w.Flush() | ||||
| } | ||||
| 
 | ||||
| func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) { | ||||
| 	defer func() { | ||||
| 		if r := recover(); r != nil { | ||||
| 			if terr, ok := r.(tomlEncodeError); ok { | ||||
| 				err = terr.error | ||||
| 				return | ||||
| 			} | ||||
| 			panic(r) | ||||
| 		} | ||||
| 	}() | ||||
| 	enc.encode(key, rv) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (enc *Encoder) encode(key Key, rv reflect.Value) { | ||||
| 	// Special case. Time needs to be in ISO8601 format.
 | ||||
| 	// Special case. If we can marshal the type to text, then we used that.
 | ||||
| 	// Basically, this prevents the encoder for handling these types as
 | ||||
| 	// generic structs (or whatever the underlying type of a TextMarshaler is).
 | ||||
| 	switch rv.Interface().(type) { | ||||
| 	case time.Time, TextMarshaler: | ||||
| 		enc.keyEqElement(key, rv) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	k := rv.Kind() | ||||
| 	switch k { | ||||
| 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, | ||||
| 		reflect.Int64, | ||||
| 		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, | ||||
| 		reflect.Uint64, | ||||
| 		reflect.Float32, reflect.Float64, reflect.String, reflect.Bool: | ||||
| 		enc.keyEqElement(key, rv) | ||||
| 	case reflect.Array, reflect.Slice: | ||||
| 		if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) { | ||||
| 			enc.eArrayOfTables(key, rv) | ||||
| 		} else { | ||||
| 			enc.keyEqElement(key, rv) | ||||
| 		} | ||||
| 	case reflect.Interface: | ||||
| 		if rv.IsNil() { | ||||
| 			return | ||||
| 		} | ||||
| 		enc.encode(key, rv.Elem()) | ||||
| 	case reflect.Map: | ||||
| 		if rv.IsNil() { | ||||
| 			return | ||||
| 		} | ||||
| 		enc.eTable(key, rv) | ||||
| 	case reflect.Ptr: | ||||
| 		if rv.IsNil() { | ||||
| 			return | ||||
| 		} | ||||
| 		enc.encode(key, rv.Elem()) | ||||
| 	case reflect.Struct: | ||||
| 		enc.eTable(key, rv) | ||||
| 	default: | ||||
| 		panic(e("unsupported type for key '%s': %s", key, k)) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // eElement encodes any value that can be an array element (primitives and
 | ||||
| // arrays).
 | ||||
| func (enc *Encoder) eElement(rv reflect.Value) { | ||||
| 	switch v := rv.Interface().(type) { | ||||
| 	case time.Time: | ||||
| 		// Special case time.Time as a primitive. Has to come before
 | ||||
| 		// TextMarshaler below because time.Time implements
 | ||||
| 		// encoding.TextMarshaler, but we need to always use UTC.
 | ||||
| 		enc.wf(v.UTC().Format("2006-01-02T15:04:05Z")) | ||||
| 		return | ||||
| 	case TextMarshaler: | ||||
| 		// Special case. Use text marshaler if it's available for this value.
 | ||||
| 		if s, err := v.MarshalText(); err != nil { | ||||
| 			encPanic(err) | ||||
| 		} else { | ||||
| 			enc.writeQuoted(string(s)) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 	switch rv.Kind() { | ||||
| 	case reflect.Bool: | ||||
| 		enc.wf(strconv.FormatBool(rv.Bool())) | ||||
| 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, | ||||
| 		reflect.Int64: | ||||
| 		enc.wf(strconv.FormatInt(rv.Int(), 10)) | ||||
| 	case reflect.Uint, reflect.Uint8, reflect.Uint16, | ||||
| 		reflect.Uint32, reflect.Uint64: | ||||
| 		enc.wf(strconv.FormatUint(rv.Uint(), 10)) | ||||
| 	case reflect.Float32: | ||||
| 		enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 32))) | ||||
| 	case reflect.Float64: | ||||
| 		enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 64))) | ||||
| 	case reflect.Array, reflect.Slice: | ||||
| 		enc.eArrayOrSliceElement(rv) | ||||
| 	case reflect.Interface: | ||||
| 		enc.eElement(rv.Elem()) | ||||
| 	case reflect.String: | ||||
| 		enc.writeQuoted(rv.String()) | ||||
| 	default: | ||||
| 		panic(e("unexpected primitive type: %s", rv.Kind())) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // By the TOML spec, all floats must have a decimal with at least one
 | ||||
| // number on either side.
 | ||||
| func floatAddDecimal(fstr string) string { | ||||
| 	if !strings.Contains(fstr, ".") { | ||||
| 		return fstr + ".0" | ||||
| 	} | ||||
| 	return fstr | ||||
| } | ||||
| 
 | ||||
| func (enc *Encoder) writeQuoted(s string) { | ||||
| 	enc.wf("\"%s\"", quotedReplacer.Replace(s)) | ||||
| } | ||||
| 
 | ||||
| func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) { | ||||
| 	length := rv.Len() | ||||
| 	enc.wf("[") | ||||
| 	for i := 0; i < length; i++ { | ||||
| 		elem := rv.Index(i) | ||||
| 		enc.eElement(elem) | ||||
| 		if i != length-1 { | ||||
| 			enc.wf(", ") | ||||
| 		} | ||||
| 	} | ||||
| 	enc.wf("]") | ||||
| } | ||||
| 
 | ||||
| func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) { | ||||
| 	if len(key) == 0 { | ||||
| 		encPanic(errNoKey) | ||||
| 	} | ||||
| 	for i := 0; i < rv.Len(); i++ { | ||||
| 		trv := rv.Index(i) | ||||
| 		if isNil(trv) { | ||||
| 			continue | ||||
| 		} | ||||
| 		panicIfInvalidKey(key) | ||||
| 		enc.newline() | ||||
| 		enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll()) | ||||
| 		enc.newline() | ||||
| 		enc.eMapOrStruct(key, trv) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (enc *Encoder) eTable(key Key, rv reflect.Value) { | ||||
| 	panicIfInvalidKey(key) | ||||
| 	if len(key) == 1 { | ||||
| 		// Output an extra newline between top-level tables.
 | ||||
| 		// (The newline isn't written if nothing else has been written though.)
 | ||||
| 		enc.newline() | ||||
| 	} | ||||
| 	if len(key) > 0 { | ||||
| 		enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll()) | ||||
| 		enc.newline() | ||||
| 	} | ||||
| 	enc.eMapOrStruct(key, rv) | ||||
| } | ||||
| 
 | ||||
| func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) { | ||||
| 	switch rv := eindirect(rv); rv.Kind() { | ||||
| 	case reflect.Map: | ||||
| 		enc.eMap(key, rv) | ||||
| 	case reflect.Struct: | ||||
| 		enc.eStruct(key, rv) | ||||
| 	default: | ||||
| 		panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String()) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (enc *Encoder) eMap(key Key, rv reflect.Value) { | ||||
| 	rt := rv.Type() | ||||
| 	if rt.Key().Kind() != reflect.String { | ||||
| 		encPanic(errNonString) | ||||
| 	} | ||||
| 
 | ||||
| 	// Sort keys so that we have deterministic output. And write keys directly
 | ||||
| 	// underneath this key first, before writing sub-structs or sub-maps.
 | ||||
| 	var mapKeysDirect, mapKeysSub []string | ||||
| 	for _, mapKey := range rv.MapKeys() { | ||||
| 		k := mapKey.String() | ||||
| 		if typeIsHash(tomlTypeOfGo(rv.MapIndex(mapKey))) { | ||||
| 			mapKeysSub = append(mapKeysSub, k) | ||||
| 		} else { | ||||
| 			mapKeysDirect = append(mapKeysDirect, k) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	var writeMapKeys = func(mapKeys []string) { | ||||
| 		sort.Strings(mapKeys) | ||||
| 		for _, mapKey := range mapKeys { | ||||
| 			mrv := rv.MapIndex(reflect.ValueOf(mapKey)) | ||||
| 			if isNil(mrv) { | ||||
| 				// Don't write anything for nil fields.
 | ||||
| 				continue | ||||
| 			} | ||||
| 			enc.encode(key.add(mapKey), mrv) | ||||
| 		} | ||||
| 	} | ||||
| 	writeMapKeys(mapKeysDirect) | ||||
| 	writeMapKeys(mapKeysSub) | ||||
| } | ||||
| 
 | ||||
| func (enc *Encoder) eStruct(key Key, rv reflect.Value) { | ||||
| 	// 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
 | ||||
| 	// table (not the one we're writing here).
 | ||||
| 	rt := rv.Type() | ||||
| 	var fieldsDirect, fieldsSub [][]int | ||||
| 	var 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++ { | ||||
| 			f := rt.Field(i) | ||||
| 			// skip unexported fields
 | ||||
| 			if f.PkgPath != "" && !f.Anonymous { | ||||
| 				continue | ||||
| 			} | ||||
| 			frv := rv.Field(i) | ||||
| 			if f.Anonymous { | ||||
| 				t := f.Type | ||||
| 				switch t.Kind() { | ||||
| 				case reflect.Struct: | ||||
| 					// Treat anonymous struct fields with
 | ||||
| 					// tag names as though they are not
 | ||||
| 					// anonymous, like encoding/json does.
 | ||||
| 					if getOptions(f.Tag).name == "" { | ||||
| 						addFields(t, frv, f.Index) | ||||
| 						continue | ||||
| 					} | ||||
| 				case reflect.Ptr: | ||||
| 					if t.Elem().Kind() == reflect.Struct && | ||||
| 						getOptions(f.Tag).name == "" { | ||||
| 						if !frv.IsNil() { | ||||
| 							addFields(t.Elem(), frv.Elem(), f.Index) | ||||
| 						} | ||||
| 						continue | ||||
| 					} | ||||
| 					// Fall through to the normal field encoding logic below
 | ||||
| 					// for non-struct anonymous fields.
 | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			if typeIsHash(tomlTypeOfGo(frv)) { | ||||
| 				fieldsSub = append(fieldsSub, append(start, f.Index...)) | ||||
| 			} else { | ||||
| 				fieldsDirect = append(fieldsDirect, append(start, f.Index...)) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	addFields(rt, rv, nil) | ||||
| 
 | ||||
| 	var writeFields = func(fields [][]int) { | ||||
| 		for _, fieldIndex := range fields { | ||||
| 			sft := rt.FieldByIndex(fieldIndex) | ||||
| 			sf := rv.FieldByIndex(fieldIndex) | ||||
| 			if isNil(sf) { | ||||
| 				// Don't write anything for nil fields.
 | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			opts := getOptions(sft.Tag) | ||||
| 			if opts.skip { | ||||
| 				continue | ||||
| 			} | ||||
| 			keyName := sft.Name | ||||
| 			if opts.name != "" { | ||||
| 				keyName = opts.name | ||||
| 			} | ||||
| 			if opts.omitempty && isEmpty(sf) { | ||||
| 				continue | ||||
| 			} | ||||
| 			if opts.omitzero && isZero(sf) { | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			enc.encode(key.add(keyName), sf) | ||||
| 		} | ||||
| 	} | ||||
| 	writeFields(fieldsDirect) | ||||
| 	writeFields(fieldsSub) | ||||
| } | ||||
| 
 | ||||
| // tomlTypeName returns the TOML type name of the Go value's type. It is
 | ||||
| // used to determine whether the types of array elements are mixed (which is
 | ||||
| // forbidden). If the Go value is nil, then it is illegal for it to be an array
 | ||||
| // element, and valueIsNil is returned as true.
 | ||||
| 
 | ||||
| // Returns the TOML type of a Go value. The type may be `nil`, which means
 | ||||
| // no concrete TOML type could be found.
 | ||||
| func tomlTypeOfGo(rv reflect.Value) tomlType { | ||||
| 	if isNil(rv) || !rv.IsValid() { | ||||
| 		return nil | ||||
| 	} | ||||
| 	switch rv.Kind() { | ||||
| 	case reflect.Bool: | ||||
| 		return tomlBool | ||||
| 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, | ||||
| 		reflect.Int64, | ||||
| 		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, | ||||
| 		reflect.Uint64: | ||||
| 		return tomlInteger | ||||
| 	case reflect.Float32, reflect.Float64: | ||||
| 		return tomlFloat | ||||
| 	case reflect.Array, reflect.Slice: | ||||
| 		if typeEqual(tomlHash, tomlArrayType(rv)) { | ||||
| 			return tomlArrayHash | ||||
| 		} | ||||
| 		return tomlArray | ||||
| 	case reflect.Ptr, reflect.Interface: | ||||
| 		return tomlTypeOfGo(rv.Elem()) | ||||
| 	case reflect.String: | ||||
| 		return tomlString | ||||
| 	case reflect.Map: | ||||
| 		return tomlHash | ||||
| 	case reflect.Struct: | ||||
| 		switch rv.Interface().(type) { | ||||
| 		case time.Time: | ||||
| 			return tomlDatetime | ||||
| 		case TextMarshaler: | ||||
| 			return tomlString | ||||
| 		default: | ||||
| 			return tomlHash | ||||
| 		} | ||||
| 	default: | ||||
| 		panic("unexpected reflect.Kind: " + rv.Kind().String()) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // tomlArrayType returns the element type of a TOML array. The type returned
 | ||||
| // may be nil if it cannot be determined (e.g., a nil slice or a zero length
 | ||||
| // slize). This function may also panic if it finds a type that cannot be
 | ||||
| // expressed in TOML (such as nil elements, heterogeneous arrays or directly
 | ||||
| // nested arrays of tables).
 | ||||
| func tomlArrayType(rv reflect.Value) tomlType { | ||||
| 	if isNil(rv) || !rv.IsValid() || rv.Len() == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	firstType := tomlTypeOfGo(rv.Index(0)) | ||||
| 	if firstType == nil { | ||||
| 		encPanic(errArrayNilElement) | ||||
| 	} | ||||
| 
 | ||||
| 	rvlen := rv.Len() | ||||
| 	for i := 1; i < rvlen; i++ { | ||||
| 		elem := rv.Index(i) | ||||
| 		switch elemType := tomlTypeOfGo(elem); { | ||||
| 		case elemType == nil: | ||||
| 			encPanic(errArrayNilElement) | ||||
| 		case !typeEqual(firstType, elemType): | ||||
| 			encPanic(errArrayMixedElementTypes) | ||||
| 		} | ||||
| 	} | ||||
| 	// If we have a nested array, then we must make sure that the nested
 | ||||
| 	// array contains ONLY primitives.
 | ||||
| 	// This checks arbitrarily nested arrays.
 | ||||
| 	if typeEqual(firstType, tomlArray) || typeEqual(firstType, tomlArrayHash) { | ||||
| 		nest := tomlArrayType(eindirect(rv.Index(0))) | ||||
| 		if typeEqual(nest, tomlHash) || typeEqual(nest, tomlArrayHash) { | ||||
| 			encPanic(errArrayNoTable) | ||||
| 		} | ||||
| 	} | ||||
| 	return firstType | ||||
| } | ||||
| 
 | ||||
| type tagOptions struct { | ||||
| 	skip      bool // "-"
 | ||||
| 	name      string | ||||
| 	omitempty bool | ||||
| 	omitzero  bool | ||||
| } | ||||
| 
 | ||||
| func getOptions(tag reflect.StructTag) tagOptions { | ||||
| 	t := tag.Get("toml") | ||||
| 	if t == "-" { | ||||
| 		return tagOptions{skip: true} | ||||
| 	} | ||||
| 	var opts tagOptions | ||||
| 	parts := strings.Split(t, ",") | ||||
| 	opts.name = parts[0] | ||||
| 	for _, s := range parts[1:] { | ||||
| 		switch s { | ||||
| 		case "omitempty": | ||||
| 			opts.omitempty = true | ||||
| 		case "omitzero": | ||||
| 			opts.omitzero = true | ||||
| 		} | ||||
| 	} | ||||
| 	return opts | ||||
| } | ||||
| 
 | ||||
| func isZero(rv reflect.Value) bool { | ||||
| 	switch rv.Kind() { | ||||
| 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | ||||
| 		return rv.Int() == 0 | ||||
| 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | ||||
| 		return rv.Uint() == 0 | ||||
| 	case reflect.Float32, reflect.Float64: | ||||
| 		return rv.Float() == 0.0 | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| func isEmpty(rv reflect.Value) bool { | ||||
| 	switch rv.Kind() { | ||||
| 	case reflect.Array, reflect.Slice, reflect.Map, reflect.String: | ||||
| 		return rv.Len() == 0 | ||||
| 	case reflect.Bool: | ||||
| 		return !rv.Bool() | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| func (enc *Encoder) newline() { | ||||
| 	if enc.hasWritten { | ||||
| 		enc.wf("\n") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (enc *Encoder) keyEqElement(key Key, val reflect.Value) { | ||||
| 	if len(key) == 0 { | ||||
| 		encPanic(errNoKey) | ||||
| 	} | ||||
| 	panicIfInvalidKey(key) | ||||
| 	enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1)) | ||||
| 	enc.eElement(val) | ||||
| 	enc.newline() | ||||
| } | ||||
| 
 | ||||
| func (enc *Encoder) wf(format string, v ...interface{}) { | ||||
| 	if _, err := fmt.Fprintf(enc.w, format, v...); err != nil { | ||||
| 		encPanic(err) | ||||
| 	} | ||||
| 	enc.hasWritten = true | ||||
| } | ||||
| 
 | ||||
| func (enc *Encoder) indentStr(key Key) string { | ||||
| 	return strings.Repeat(enc.Indent, len(key)-1) | ||||
| } | ||||
| 
 | ||||
| func encPanic(err error) { | ||||
| 	panic(tomlEncodeError{err}) | ||||
| } | ||||
| 
 | ||||
| func eindirect(v reflect.Value) reflect.Value { | ||||
| 	switch v.Kind() { | ||||
| 	case reflect.Ptr, reflect.Interface: | ||||
| 		return eindirect(v.Elem()) | ||||
| 	default: | ||||
| 		return v | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func isNil(rv reflect.Value) bool { | ||||
| 	switch rv.Kind() { | ||||
| 	case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: | ||||
| 		return rv.IsNil() | ||||
| 	default: | ||||
| 		return false | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func panicIfInvalidKey(key Key) { | ||||
| 	for _, k := range key { | ||||
| 		if len(k) == 0 { | ||||
| 			encPanic(e("Key '%s' is not a valid table name. Key names "+ | ||||
| 				"cannot be empty.", key.maybeQuotedAll())) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func isValidKeyName(s string) bool { | ||||
| 	return len(s) != 0 | ||||
| } | ||||
							
								
								
									
										19
									
								
								vendor/github.com/BurntSushi/toml/encoding_types.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								vendor/github.com/BurntSushi/toml/encoding_types.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,19 +0,0 @@ | ||||
| // +build go1.2
 | ||||
| 
 | ||||
| package toml | ||||
| 
 | ||||
| // In order to support Go 1.1, we define our own TextMarshaler and
 | ||||
| // TextUnmarshaler types. For Go 1.2+, we just alias them with the
 | ||||
| // standard library interfaces.
 | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding" | ||||
| ) | ||||
| 
 | ||||
| // TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here
 | ||||
| // so that Go 1.1 can be supported.
 | ||||
| type TextMarshaler encoding.TextMarshaler | ||||
| 
 | ||||
| // TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined
 | ||||
| // here so that Go 1.1 can be supported.
 | ||||
| type TextUnmarshaler encoding.TextUnmarshaler | ||||
							
								
								
									
										18
									
								
								vendor/github.com/BurntSushi/toml/encoding_types_1.1.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								vendor/github.com/BurntSushi/toml/encoding_types_1.1.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,18 +0,0 @@ | ||||
| // +build !go1.2
 | ||||
| 
 | ||||
| package toml | ||||
| 
 | ||||
| // These interfaces were introduced in Go 1.2, so we add them manually when
 | ||||
| // compiling for Go 1.1.
 | ||||
| 
 | ||||
| // TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here
 | ||||
| // so that Go 1.1 can be supported.
 | ||||
| type TextMarshaler interface { | ||||
| 	MarshalText() (text []byte, err error) | ||||
| } | ||||
| 
 | ||||
| // TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined
 | ||||
| // here so that Go 1.1 can be supported.
 | ||||
| type TextUnmarshaler interface { | ||||
| 	UnmarshalText(text []byte) error | ||||
| } | ||||
							
								
								
									
										953
									
								
								vendor/github.com/BurntSushi/toml/lex.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										953
									
								
								vendor/github.com/BurntSushi/toml/lex.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,953 +0,0 @@ | ||||
| package toml | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 	"unicode" | ||||
| 	"unicode/utf8" | ||||
| ) | ||||
| 
 | ||||
| type itemType int | ||||
| 
 | ||||
| const ( | ||||
| 	itemError itemType = iota | ||||
| 	itemNIL            // used in the parser to indicate no type
 | ||||
| 	itemEOF | ||||
| 	itemText | ||||
| 	itemString | ||||
| 	itemRawString | ||||
| 	itemMultilineString | ||||
| 	itemRawMultilineString | ||||
| 	itemBool | ||||
| 	itemInteger | ||||
| 	itemFloat | ||||
| 	itemDatetime | ||||
| 	itemArray // the start of an array
 | ||||
| 	itemArrayEnd | ||||
| 	itemTableStart | ||||
| 	itemTableEnd | ||||
| 	itemArrayTableStart | ||||
| 	itemArrayTableEnd | ||||
| 	itemKeyStart | ||||
| 	itemCommentStart | ||||
| 	itemInlineTableStart | ||||
| 	itemInlineTableEnd | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	eof              = 0 | ||||
| 	comma            = ',' | ||||
| 	tableStart       = '[' | ||||
| 	tableEnd         = ']' | ||||
| 	arrayTableStart  = '[' | ||||
| 	arrayTableEnd    = ']' | ||||
| 	tableSep         = '.' | ||||
| 	keySep           = '=' | ||||
| 	arrayStart       = '[' | ||||
| 	arrayEnd         = ']' | ||||
| 	commentStart     = '#' | ||||
| 	stringStart      = '"' | ||||
| 	stringEnd        = '"' | ||||
| 	rawStringStart   = '\'' | ||||
| 	rawStringEnd     = '\'' | ||||
| 	inlineTableStart = '{' | ||||
| 	inlineTableEnd   = '}' | ||||
| ) | ||||
| 
 | ||||
| type stateFn func(lx *lexer) stateFn | ||||
| 
 | ||||
| type lexer struct { | ||||
| 	input string | ||||
| 	start int | ||||
| 	pos   int | ||||
| 	line  int | ||||
| 	state stateFn | ||||
| 	items chan item | ||||
| 
 | ||||
| 	// Allow for backing up up to three runes.
 | ||||
| 	// This is necessary because TOML contains 3-rune tokens (""" and ''').
 | ||||
| 	prevWidths [3]int | ||||
| 	nprev      int // how many of prevWidths are in use
 | ||||
| 	// If we emit an eof, we can still back up, but it is not OK to call
 | ||||
| 	// next again.
 | ||||
| 	atEOF bool | ||||
| 
 | ||||
| 	// A stack of state functions used to maintain context.
 | ||||
| 	// The idea is to reuse parts of the state machine in various places.
 | ||||
| 	// For example, values can appear at the top level or within arbitrarily
 | ||||
| 	// nested arrays. The last state on the stack is used after a value has
 | ||||
| 	// been lexed. Similarly for comments.
 | ||||
| 	stack []stateFn | ||||
| } | ||||
| 
 | ||||
| type item struct { | ||||
| 	typ  itemType | ||||
| 	val  string | ||||
| 	line int | ||||
| } | ||||
| 
 | ||||
| func (lx *lexer) nextItem() item { | ||||
| 	for { | ||||
| 		select { | ||||
| 		case item := <-lx.items: | ||||
| 			return item | ||||
| 		default: | ||||
| 			lx.state = lx.state(lx) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func lex(input string) *lexer { | ||||
| 	lx := &lexer{ | ||||
| 		input: input, | ||||
| 		state: lexTop, | ||||
| 		line:  1, | ||||
| 		items: make(chan item, 10), | ||||
| 		stack: make([]stateFn, 0, 10), | ||||
| 	} | ||||
| 	return lx | ||||
| } | ||||
| 
 | ||||
| func (lx *lexer) push(state stateFn) { | ||||
| 	lx.stack = append(lx.stack, state) | ||||
| } | ||||
| 
 | ||||
| func (lx *lexer) pop() stateFn { | ||||
| 	if len(lx.stack) == 0 { | ||||
| 		return lx.errorf("BUG in lexer: no states to pop") | ||||
| 	} | ||||
| 	last := lx.stack[len(lx.stack)-1] | ||||
| 	lx.stack = lx.stack[0 : len(lx.stack)-1] | ||||
| 	return last | ||||
| } | ||||
| 
 | ||||
| func (lx *lexer) current() string { | ||||
| 	return lx.input[lx.start:lx.pos] | ||||
| } | ||||
| 
 | ||||
| func (lx *lexer) emit(typ itemType) { | ||||
| 	lx.items <- item{typ, lx.current(), lx.line} | ||||
| 	lx.start = lx.pos | ||||
| } | ||||
| 
 | ||||
| func (lx *lexer) emitTrim(typ itemType) { | ||||
| 	lx.items <- item{typ, strings.TrimSpace(lx.current()), lx.line} | ||||
| 	lx.start = lx.pos | ||||
| } | ||||
| 
 | ||||
| func (lx *lexer) next() (r rune) { | ||||
| 	if lx.atEOF { | ||||
| 		panic("next called after EOF") | ||||
| 	} | ||||
| 	if lx.pos >= len(lx.input) { | ||||
| 		lx.atEOF = true | ||||
| 		return eof | ||||
| 	} | ||||
| 
 | ||||
| 	if lx.input[lx.pos] == '\n' { | ||||
| 		lx.line++ | ||||
| 	} | ||||
| 	lx.prevWidths[2] = lx.prevWidths[1] | ||||
| 	lx.prevWidths[1] = lx.prevWidths[0] | ||||
| 	if lx.nprev < 3 { | ||||
| 		lx.nprev++ | ||||
| 	} | ||||
| 	r, w := utf8.DecodeRuneInString(lx.input[lx.pos:]) | ||||
| 	lx.prevWidths[0] = w | ||||
| 	lx.pos += w | ||||
| 	return r | ||||
| } | ||||
| 
 | ||||
| // ignore skips over the pending input before this point.
 | ||||
| func (lx *lexer) ignore() { | ||||
| 	lx.start = lx.pos | ||||
| } | ||||
| 
 | ||||
| // backup steps back one rune. Can be called only twice between calls to next.
 | ||||
| func (lx *lexer) backup() { | ||||
| 	if lx.atEOF { | ||||
| 		lx.atEOF = false | ||||
| 		return | ||||
| 	} | ||||
| 	if lx.nprev < 1 { | ||||
| 		panic("backed up too far") | ||||
| 	} | ||||
| 	w := lx.prevWidths[0] | ||||
| 	lx.prevWidths[0] = lx.prevWidths[1] | ||||
| 	lx.prevWidths[1] = lx.prevWidths[2] | ||||
| 	lx.nprev-- | ||||
| 	lx.pos -= w | ||||
| 	if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' { | ||||
| 		lx.line-- | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // accept consumes the next rune if it's equal to `valid`.
 | ||||
| func (lx *lexer) accept(valid rune) bool { | ||||
| 	if lx.next() == valid { | ||||
| 		return true | ||||
| 	} | ||||
| 	lx.backup() | ||||
| 	return false | ||||
| } | ||||
| 
 | ||||
| // peek returns but does not consume the next rune in the input.
 | ||||
| func (lx *lexer) peek() rune { | ||||
| 	r := lx.next() | ||||
| 	lx.backup() | ||||
| 	return r | ||||
| } | ||||
| 
 | ||||
| // skip ignores all input that matches the given predicate.
 | ||||
| func (lx *lexer) skip(pred func(rune) bool) { | ||||
| 	for { | ||||
| 		r := lx.next() | ||||
| 		if pred(r) { | ||||
| 			continue | ||||
| 		} | ||||
| 		lx.backup() | ||||
| 		lx.ignore() | ||||
| 		return | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // errorf stops all lexing by emitting an error and returning `nil`.
 | ||||
| // Note that any value that is a character is escaped if it's a special
 | ||||
| // character (newlines, tabs, etc.).
 | ||||
| func (lx *lexer) errorf(format string, values ...interface{}) stateFn { | ||||
| 	lx.items <- item{ | ||||
| 		itemError, | ||||
| 		fmt.Sprintf(format, values...), | ||||
| 		lx.line, | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // lexTop consumes elements at the top level of TOML data.
 | ||||
| func lexTop(lx *lexer) stateFn { | ||||
| 	r := lx.next() | ||||
| 	if isWhitespace(r) || isNL(r) { | ||||
| 		return lexSkip(lx, lexTop) | ||||
| 	} | ||||
| 	switch r { | ||||
| 	case commentStart: | ||||
| 		lx.push(lexTop) | ||||
| 		return lexCommentStart | ||||
| 	case tableStart: | ||||
| 		return lexTableStart | ||||
| 	case eof: | ||||
| 		if lx.pos > lx.start { | ||||
| 			return lx.errorf("unexpected EOF") | ||||
| 		} | ||||
| 		lx.emit(itemEOF) | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	// At this point, the only valid item can be a key, so we back up
 | ||||
| 	// and let the key lexer do the rest.
 | ||||
| 	lx.backup() | ||||
| 	lx.push(lexTopEnd) | ||||
| 	return lexKeyStart | ||||
| } | ||||
| 
 | ||||
| // lexTopEnd is entered whenever a top-level item has been consumed. (A value
 | ||||
| // or a table.) It must see only whitespace, and will turn back to lexTop
 | ||||
| // upon a newline. If it sees EOF, it will quit the lexer successfully.
 | ||||
| func lexTopEnd(lx *lexer) stateFn { | ||||
| 	r := lx.next() | ||||
| 	switch { | ||||
| 	case r == commentStart: | ||||
| 		// a comment will read to a newline for us.
 | ||||
| 		lx.push(lexTop) | ||||
| 		return lexCommentStart | ||||
| 	case isWhitespace(r): | ||||
| 		return lexTopEnd | ||||
| 	case isNL(r): | ||||
| 		lx.ignore() | ||||
| 		return lexTop | ||||
| 	case r == eof: | ||||
| 		lx.emit(itemEOF) | ||||
| 		return nil | ||||
| 	} | ||||
| 	return lx.errorf("expected a top-level item to end with a newline, "+ | ||||
| 		"comment, or EOF, but got %q instead", r) | ||||
| } | ||||
| 
 | ||||
| // lexTable lexes the beginning of a table. Namely, it makes sure that
 | ||||
| // it starts with a character other than '.' and ']'.
 | ||||
| // It assumes that '[' has already been consumed.
 | ||||
| // It also handles the case that this is an item in an array of tables.
 | ||||
| // e.g., '[[name]]'.
 | ||||
| func lexTableStart(lx *lexer) stateFn { | ||||
| 	if lx.peek() == arrayTableStart { | ||||
| 		lx.next() | ||||
| 		lx.emit(itemArrayTableStart) | ||||
| 		lx.push(lexArrayTableEnd) | ||||
| 	} else { | ||||
| 		lx.emit(itemTableStart) | ||||
| 		lx.push(lexTableEnd) | ||||
| 	} | ||||
| 	return lexTableNameStart | ||||
| } | ||||
| 
 | ||||
| func lexTableEnd(lx *lexer) stateFn { | ||||
| 	lx.emit(itemTableEnd) | ||||
| 	return lexTopEnd | ||||
| } | ||||
| 
 | ||||
| func lexArrayTableEnd(lx *lexer) stateFn { | ||||
| 	if r := lx.next(); r != arrayTableEnd { | ||||
| 		return lx.errorf("expected end of table array name delimiter %q, "+ | ||||
| 			"but got %q instead", arrayTableEnd, r) | ||||
| 	} | ||||
| 	lx.emit(itemArrayTableEnd) | ||||
| 	return lexTopEnd | ||||
| } | ||||
| 
 | ||||
| func lexTableNameStart(lx *lexer) stateFn { | ||||
| 	lx.skip(isWhitespace) | ||||
| 	switch r := lx.peek(); { | ||||
| 	case r == tableEnd || r == eof: | ||||
| 		return lx.errorf("unexpected end of table name " + | ||||
| 			"(table names cannot be empty)") | ||||
| 	case r == tableSep: | ||||
| 		return lx.errorf("unexpected table separator " + | ||||
| 			"(table names cannot be empty)") | ||||
| 	case r == stringStart || r == rawStringStart: | ||||
| 		lx.ignore() | ||||
| 		lx.push(lexTableNameEnd) | ||||
| 		return lexValue // reuse string lexing
 | ||||
| 	default: | ||||
| 		return lexBareTableName | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // lexBareTableName lexes the name of a table. It assumes that at least one
 | ||||
| // valid character for the table has already been read.
 | ||||
| func lexBareTableName(lx *lexer) stateFn { | ||||
| 	r := lx.next() | ||||
| 	if isBareKeyChar(r) { | ||||
| 		return lexBareTableName | ||||
| 	} | ||||
| 	lx.backup() | ||||
| 	lx.emit(itemText) | ||||
| 	return lexTableNameEnd | ||||
| } | ||||
| 
 | ||||
| // lexTableNameEnd reads the end of a piece of a table name, optionally
 | ||||
| // consuming whitespace.
 | ||||
| func lexTableNameEnd(lx *lexer) stateFn { | ||||
| 	lx.skip(isWhitespace) | ||||
| 	switch r := lx.next(); { | ||||
| 	case isWhitespace(r): | ||||
| 		return lexTableNameEnd | ||||
| 	case r == tableSep: | ||||
| 		lx.ignore() | ||||
| 		return lexTableNameStart | ||||
| 	case r == tableEnd: | ||||
| 		return lx.pop() | ||||
| 	default: | ||||
| 		return lx.errorf("expected '.' or ']' to end table name, "+ | ||||
| 			"but got %q instead", r) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // lexKeyStart consumes a key name up until the first non-whitespace character.
 | ||||
| // lexKeyStart will ignore whitespace.
 | ||||
| func lexKeyStart(lx *lexer) stateFn { | ||||
| 	r := lx.peek() | ||||
| 	switch { | ||||
| 	case r == keySep: | ||||
| 		return lx.errorf("unexpected key separator %q", keySep) | ||||
| 	case isWhitespace(r) || isNL(r): | ||||
| 		lx.next() | ||||
| 		return lexSkip(lx, lexKeyStart) | ||||
| 	case r == stringStart || r == rawStringStart: | ||||
| 		lx.ignore() | ||||
| 		lx.emit(itemKeyStart) | ||||
| 		lx.push(lexKeyEnd) | ||||
| 		return lexValue // reuse string lexing
 | ||||
| 	default: | ||||
| 		lx.ignore() | ||||
| 		lx.emit(itemKeyStart) | ||||
| 		return lexBareKey | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // lexBareKey consumes the text of a bare key. Assumes that the first character
 | ||||
| // (which is not whitespace) has not yet been consumed.
 | ||||
| func lexBareKey(lx *lexer) stateFn { | ||||
| 	switch r := lx.next(); { | ||||
| 	case isBareKeyChar(r): | ||||
| 		return lexBareKey | ||||
| 	case isWhitespace(r): | ||||
| 		lx.backup() | ||||
| 		lx.emit(itemText) | ||||
| 		return lexKeyEnd | ||||
| 	case r == keySep: | ||||
| 		lx.backup() | ||||
| 		lx.emit(itemText) | ||||
| 		return lexKeyEnd | ||||
| 	default: | ||||
| 		return lx.errorf("bare keys cannot contain %q", r) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // lexKeyEnd consumes the end of a key and trims whitespace (up to the key
 | ||||
| // separator).
 | ||||
| func lexKeyEnd(lx *lexer) stateFn { | ||||
| 	switch r := lx.next(); { | ||||
| 	case r == keySep: | ||||
| 		return lexSkip(lx, lexValue) | ||||
| 	case isWhitespace(r): | ||||
| 		return lexSkip(lx, lexKeyEnd) | ||||
| 	default: | ||||
| 		return lx.errorf("expected key separator %q, but got %q instead", | ||||
| 			keySep, r) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // lexValue starts the consumption of a value anywhere a value is expected.
 | ||||
| // lexValue will ignore whitespace.
 | ||||
| // After a value is lexed, the last state on the next is popped and returned.
 | ||||
| func lexValue(lx *lexer) stateFn { | ||||
| 	// We allow whitespace to precede a value, but NOT newlines.
 | ||||
| 	// In array syntax, the array states are responsible for ignoring newlines.
 | ||||
| 	r := lx.next() | ||||
| 	switch { | ||||
| 	case isWhitespace(r): | ||||
| 		return lexSkip(lx, lexValue) | ||||
| 	case isDigit(r): | ||||
| 		lx.backup() // avoid an extra state and use the same as above
 | ||||
| 		return lexNumberOrDateStart | ||||
| 	} | ||||
| 	switch r { | ||||
| 	case arrayStart: | ||||
| 		lx.ignore() | ||||
| 		lx.emit(itemArray) | ||||
| 		return lexArrayValue | ||||
| 	case inlineTableStart: | ||||
| 		lx.ignore() | ||||
| 		lx.emit(itemInlineTableStart) | ||||
| 		return lexInlineTableValue | ||||
| 	case stringStart: | ||||
| 		if lx.accept(stringStart) { | ||||
| 			if lx.accept(stringStart) { | ||||
| 				lx.ignore() // Ignore """
 | ||||
| 				return lexMultilineString | ||||
| 			} | ||||
| 			lx.backup() | ||||
| 		} | ||||
| 		lx.ignore() // ignore the '"'
 | ||||
| 		return lexString | ||||
| 	case rawStringStart: | ||||
| 		if lx.accept(rawStringStart) { | ||||
| 			if lx.accept(rawStringStart) { | ||||
| 				lx.ignore() // Ignore """
 | ||||
| 				return lexMultilineRawString | ||||
| 			} | ||||
| 			lx.backup() | ||||
| 		} | ||||
| 		lx.ignore() // ignore the "'"
 | ||||
| 		return lexRawString | ||||
| 	case '+', '-': | ||||
| 		return lexNumberStart | ||||
| 	case '.': // special error case, be kind to users
 | ||||
| 		return lx.errorf("floats must start with a digit, not '.'") | ||||
| 	} | ||||
| 	if unicode.IsLetter(r) { | ||||
| 		// Be permissive here; lexBool will give a nice error if the
 | ||||
| 		// user wrote something like
 | ||||
| 		//   x = foo
 | ||||
| 		// (i.e. not 'true' or 'false' but is something else word-like.)
 | ||||
| 		lx.backup() | ||||
| 		return lexBool | ||||
| 	} | ||||
| 	return lx.errorf("expected value but found %q instead", r) | ||||
| } | ||||
| 
 | ||||
| // lexArrayValue consumes one value in an array. It assumes that '[' or ','
 | ||||
| // have already been consumed. All whitespace and newlines are ignored.
 | ||||
| func lexArrayValue(lx *lexer) stateFn { | ||||
| 	r := lx.next() | ||||
| 	switch { | ||||
| 	case isWhitespace(r) || isNL(r): | ||||
| 		return lexSkip(lx, lexArrayValue) | ||||
| 	case r == commentStart: | ||||
| 		lx.push(lexArrayValue) | ||||
| 		return lexCommentStart | ||||
| 	case r == comma: | ||||
| 		return lx.errorf("unexpected comma") | ||||
| 	case r == arrayEnd: | ||||
| 		// NOTE(caleb): The spec isn't clear about whether you can have
 | ||||
| 		// a trailing comma or not, so we'll allow it.
 | ||||
| 		return lexArrayEnd | ||||
| 	} | ||||
| 
 | ||||
| 	lx.backup() | ||||
| 	lx.push(lexArrayValueEnd) | ||||
| 	return lexValue | ||||
| } | ||||
| 
 | ||||
| // lexArrayValueEnd consumes everything between the end of an array value and
 | ||||
| // the next value (or the end of the array): it ignores whitespace and newlines
 | ||||
| // and expects either a ',' or a ']'.
 | ||||
| func lexArrayValueEnd(lx *lexer) stateFn { | ||||
| 	r := lx.next() | ||||
| 	switch { | ||||
| 	case isWhitespace(r) || isNL(r): | ||||
| 		return lexSkip(lx, lexArrayValueEnd) | ||||
| 	case r == commentStart: | ||||
| 		lx.push(lexArrayValueEnd) | ||||
| 		return lexCommentStart | ||||
| 	case r == comma: | ||||
| 		lx.ignore() | ||||
| 		return lexArrayValue // move on to the next value
 | ||||
| 	case r == arrayEnd: | ||||
| 		return lexArrayEnd | ||||
| 	} | ||||
| 	return lx.errorf( | ||||
| 		"expected a comma or array terminator %q, but got %q instead", | ||||
| 		arrayEnd, r, | ||||
| 	) | ||||
| } | ||||
| 
 | ||||
| // lexArrayEnd finishes the lexing of an array.
 | ||||
| // It assumes that a ']' has just been consumed.
 | ||||
| func lexArrayEnd(lx *lexer) stateFn { | ||||
| 	lx.ignore() | ||||
| 	lx.emit(itemArrayEnd) | ||||
| 	return lx.pop() | ||||
| } | ||||
| 
 | ||||
| // lexInlineTableValue consumes one key/value pair in an inline table.
 | ||||
| // It assumes that '{' or ',' have already been consumed. Whitespace is ignored.
 | ||||
| func lexInlineTableValue(lx *lexer) stateFn { | ||||
| 	r := lx.next() | ||||
| 	switch { | ||||
| 	case isWhitespace(r): | ||||
| 		return lexSkip(lx, lexInlineTableValue) | ||||
| 	case isNL(r): | ||||
| 		return lx.errorf("newlines not allowed within inline tables") | ||||
| 	case r == commentStart: | ||||
| 		lx.push(lexInlineTableValue) | ||||
| 		return lexCommentStart | ||||
| 	case r == comma: | ||||
| 		return lx.errorf("unexpected comma") | ||||
| 	case r == inlineTableEnd: | ||||
| 		return lexInlineTableEnd | ||||
| 	} | ||||
| 	lx.backup() | ||||
| 	lx.push(lexInlineTableValueEnd) | ||||
| 	return lexKeyStart | ||||
| } | ||||
| 
 | ||||
| // lexInlineTableValueEnd consumes everything between the end of an inline table
 | ||||
| // key/value pair and the next pair (or the end of the table):
 | ||||
| // it ignores whitespace and expects either a ',' or a '}'.
 | ||||
| func lexInlineTableValueEnd(lx *lexer) stateFn { | ||||
| 	r := lx.next() | ||||
| 	switch { | ||||
| 	case isWhitespace(r): | ||||
| 		return lexSkip(lx, lexInlineTableValueEnd) | ||||
| 	case isNL(r): | ||||
| 		return lx.errorf("newlines not allowed within inline tables") | ||||
| 	case r == commentStart: | ||||
| 		lx.push(lexInlineTableValueEnd) | ||||
| 		return lexCommentStart | ||||
| 	case r == comma: | ||||
| 		lx.ignore() | ||||
| 		return lexInlineTableValue | ||||
| 	case r == inlineTableEnd: | ||||
| 		return lexInlineTableEnd | ||||
| 	} | ||||
| 	return lx.errorf("expected a comma or an inline table terminator %q, "+ | ||||
| 		"but got %q instead", inlineTableEnd, r) | ||||
| } | ||||
| 
 | ||||
| // lexInlineTableEnd finishes the lexing of an inline table.
 | ||||
| // It assumes that a '}' has just been consumed.
 | ||||
| func lexInlineTableEnd(lx *lexer) stateFn { | ||||
| 	lx.ignore() | ||||
| 	lx.emit(itemInlineTableEnd) | ||||
| 	return lx.pop() | ||||
| } | ||||
| 
 | ||||
| // lexString consumes the inner contents of a string. It assumes that the
 | ||||
| // beginning '"' has already been consumed and ignored.
 | ||||
| func lexString(lx *lexer) stateFn { | ||||
| 	r := lx.next() | ||||
| 	switch { | ||||
| 	case r == eof: | ||||
| 		return lx.errorf("unexpected EOF") | ||||
| 	case isNL(r): | ||||
| 		return lx.errorf("strings cannot contain newlines") | ||||
| 	case r == '\\': | ||||
| 		lx.push(lexString) | ||||
| 		return lexStringEscape | ||||
| 	case r == stringEnd: | ||||
| 		lx.backup() | ||||
| 		lx.emit(itemString) | ||||
| 		lx.next() | ||||
| 		lx.ignore() | ||||
| 		return lx.pop() | ||||
| 	} | ||||
| 	return lexString | ||||
| } | ||||
| 
 | ||||
| // lexMultilineString consumes the inner contents of a string. It assumes that
 | ||||
| // the beginning '"""' has already been consumed and ignored.
 | ||||
| func lexMultilineString(lx *lexer) stateFn { | ||||
| 	switch lx.next() { | ||||
| 	case eof: | ||||
| 		return lx.errorf("unexpected EOF") | ||||
| 	case '\\': | ||||
| 		return lexMultilineStringEscape | ||||
| 	case stringEnd: | ||||
| 		if lx.accept(stringEnd) { | ||||
| 			if lx.accept(stringEnd) { | ||||
| 				lx.backup() | ||||
| 				lx.backup() | ||||
| 				lx.backup() | ||||
| 				lx.emit(itemMultilineString) | ||||
| 				lx.next() | ||||
| 				lx.next() | ||||
| 				lx.next() | ||||
| 				lx.ignore() | ||||
| 				return lx.pop() | ||||
| 			} | ||||
| 			lx.backup() | ||||
| 		} | ||||
| 	} | ||||
| 	return lexMultilineString | ||||
| } | ||||
| 
 | ||||
| // lexRawString consumes a raw string. Nothing can be escaped in such a string.
 | ||||
| // It assumes that the beginning "'" has already been consumed and ignored.
 | ||||
| func lexRawString(lx *lexer) stateFn { | ||||
| 	r := lx.next() | ||||
| 	switch { | ||||
| 	case r == eof: | ||||
| 		return lx.errorf("unexpected EOF") | ||||
| 	case isNL(r): | ||||
| 		return lx.errorf("strings cannot contain newlines") | ||||
| 	case r == rawStringEnd: | ||||
| 		lx.backup() | ||||
| 		lx.emit(itemRawString) | ||||
| 		lx.next() | ||||
| 		lx.ignore() | ||||
| 		return lx.pop() | ||||
| 	} | ||||
| 	return lexRawString | ||||
| } | ||||
| 
 | ||||
| // lexMultilineRawString consumes a raw string. Nothing can be escaped in such
 | ||||
| // a string. It assumes that the beginning "'''" has already been consumed and
 | ||||
| // ignored.
 | ||||
| func lexMultilineRawString(lx *lexer) stateFn { | ||||
| 	switch lx.next() { | ||||
| 	case eof: | ||||
| 		return lx.errorf("unexpected EOF") | ||||
| 	case rawStringEnd: | ||||
| 		if lx.accept(rawStringEnd) { | ||||
| 			if lx.accept(rawStringEnd) { | ||||
| 				lx.backup() | ||||
| 				lx.backup() | ||||
| 				lx.backup() | ||||
| 				lx.emit(itemRawMultilineString) | ||||
| 				lx.next() | ||||
| 				lx.next() | ||||
| 				lx.next() | ||||
| 				lx.ignore() | ||||
| 				return lx.pop() | ||||
| 			} | ||||
| 			lx.backup() | ||||
| 		} | ||||
| 	} | ||||
| 	return lexMultilineRawString | ||||
| } | ||||
| 
 | ||||
| // lexMultilineStringEscape consumes an escaped character. It assumes that the
 | ||||
| // preceding '\\' has already been consumed.
 | ||||
| func lexMultilineStringEscape(lx *lexer) stateFn { | ||||
| 	// Handle the special case first:
 | ||||
| 	if isNL(lx.next()) { | ||||
| 		return lexMultilineString | ||||
| 	} | ||||
| 	lx.backup() | ||||
| 	lx.push(lexMultilineString) | ||||
| 	return lexStringEscape(lx) | ||||
| } | ||||
| 
 | ||||
| func lexStringEscape(lx *lexer) stateFn { | ||||
| 	r := lx.next() | ||||
| 	switch r { | ||||
| 	case 'b': | ||||
| 		fallthrough | ||||
| 	case 't': | ||||
| 		fallthrough | ||||
| 	case 'n': | ||||
| 		fallthrough | ||||
| 	case 'f': | ||||
| 		fallthrough | ||||
| 	case 'r': | ||||
| 		fallthrough | ||||
| 	case '"': | ||||
| 		fallthrough | ||||
| 	case '\\': | ||||
| 		return lx.pop() | ||||
| 	case 'u': | ||||
| 		return lexShortUnicodeEscape | ||||
| 	case 'U': | ||||
| 		return lexLongUnicodeEscape | ||||
| 	} | ||||
| 	return lx.errorf("invalid escape character %q; only the following "+ | ||||
| 		"escape characters are allowed: "+ | ||||
| 		`\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX`, r) | ||||
| } | ||||
| 
 | ||||
| func lexShortUnicodeEscape(lx *lexer) stateFn { | ||||
| 	var r rune | ||||
| 	for i := 0; i < 4; i++ { | ||||
| 		r = lx.next() | ||||
| 		if !isHexadecimal(r) { | ||||
| 			return lx.errorf(`expected four hexadecimal digits after '\u', `+ | ||||
| 				"but got %q instead", lx.current()) | ||||
| 		} | ||||
| 	} | ||||
| 	return lx.pop() | ||||
| } | ||||
| 
 | ||||
| func lexLongUnicodeEscape(lx *lexer) stateFn { | ||||
| 	var r rune | ||||
| 	for i := 0; i < 8; i++ { | ||||
| 		r = lx.next() | ||||
| 		if !isHexadecimal(r) { | ||||
| 			return lx.errorf(`expected eight hexadecimal digits after '\U', `+ | ||||
| 				"but got %q instead", lx.current()) | ||||
| 		} | ||||
| 	} | ||||
| 	return lx.pop() | ||||
| } | ||||
| 
 | ||||
| // lexNumberOrDateStart consumes either an integer, a float, or datetime.
 | ||||
| func lexNumberOrDateStart(lx *lexer) stateFn { | ||||
| 	r := lx.next() | ||||
| 	if isDigit(r) { | ||||
| 		return lexNumberOrDate | ||||
| 	} | ||||
| 	switch r { | ||||
| 	case '_': | ||||
| 		return lexNumber | ||||
| 	case 'e', 'E': | ||||
| 		return lexFloat | ||||
| 	case '.': | ||||
| 		return lx.errorf("floats must start with a digit, not '.'") | ||||
| 	} | ||||
| 	return lx.errorf("expected a digit but got %q", r) | ||||
| } | ||||
| 
 | ||||
| // lexNumberOrDate consumes either an integer, float or datetime.
 | ||||
| func lexNumberOrDate(lx *lexer) stateFn { | ||||
| 	r := lx.next() | ||||
| 	if isDigit(r) { | ||||
| 		return lexNumberOrDate | ||||
| 	} | ||||
| 	switch r { | ||||
| 	case '-': | ||||
| 		return lexDatetime | ||||
| 	case '_': | ||||
| 		return lexNumber | ||||
| 	case '.', 'e', 'E': | ||||
| 		return lexFloat | ||||
| 	} | ||||
| 
 | ||||
| 	lx.backup() | ||||
| 	lx.emit(itemInteger) | ||||
| 	return lx.pop() | ||||
| } | ||||
| 
 | ||||
| // lexDatetime consumes a Datetime, to a first approximation.
 | ||||
| // The parser validates that it matches one of the accepted formats.
 | ||||
| func lexDatetime(lx *lexer) stateFn { | ||||
| 	r := lx.next() | ||||
| 	if isDigit(r) { | ||||
| 		return lexDatetime | ||||
| 	} | ||||
| 	switch r { | ||||
| 	case '-', 'T', ':', '.', 'Z', '+': | ||||
| 		return lexDatetime | ||||
| 	} | ||||
| 
 | ||||
| 	lx.backup() | ||||
| 	lx.emit(itemDatetime) | ||||
| 	return lx.pop() | ||||
| } | ||||
| 
 | ||||
| // lexNumberStart consumes either an integer or a float. It assumes that a sign
 | ||||
| // has already been read, but that *no* digits have been consumed.
 | ||||
| // lexNumberStart will move to the appropriate integer or float states.
 | ||||
| func lexNumberStart(lx *lexer) stateFn { | ||||
| 	// We MUST see a digit. Even floats have to start with a digit.
 | ||||
| 	r := lx.next() | ||||
| 	if !isDigit(r) { | ||||
| 		if r == '.' { | ||||
| 			return lx.errorf("floats must start with a digit, not '.'") | ||||
| 		} | ||||
| 		return lx.errorf("expected a digit but got %q", r) | ||||
| 	} | ||||
| 	return lexNumber | ||||
| } | ||||
| 
 | ||||
| // lexNumber consumes an integer or a float after seeing the first digit.
 | ||||
| func lexNumber(lx *lexer) stateFn { | ||||
| 	r := lx.next() | ||||
| 	if isDigit(r) { | ||||
| 		return lexNumber | ||||
| 	} | ||||
| 	switch r { | ||||
| 	case '_': | ||||
| 		return lexNumber | ||||
| 	case '.', 'e', 'E': | ||||
| 		return lexFloat | ||||
| 	} | ||||
| 
 | ||||
| 	lx.backup() | ||||
| 	lx.emit(itemInteger) | ||||
| 	return lx.pop() | ||||
| } | ||||
| 
 | ||||
| // lexFloat consumes the elements of a float. It allows any sequence of
 | ||||
| // float-like characters, so floats emitted by the lexer are only a first
 | ||||
| // approximation and must be validated by the parser.
 | ||||
| func lexFloat(lx *lexer) stateFn { | ||||
| 	r := lx.next() | ||||
| 	if isDigit(r) { | ||||
| 		return lexFloat | ||||
| 	} | ||||
| 	switch r { | ||||
| 	case '_', '.', '-', '+', 'e', 'E': | ||||
| 		return lexFloat | ||||
| 	} | ||||
| 
 | ||||
| 	lx.backup() | ||||
| 	lx.emit(itemFloat) | ||||
| 	return lx.pop() | ||||
| } | ||||
| 
 | ||||
| // lexBool consumes a bool string: 'true' or 'false.
 | ||||
| func lexBool(lx *lexer) stateFn { | ||||
| 	var rs []rune | ||||
| 	for { | ||||
| 		r := lx.next() | ||||
| 		if !unicode.IsLetter(r) { | ||||
| 			lx.backup() | ||||
| 			break | ||||
| 		} | ||||
| 		rs = append(rs, r) | ||||
| 	} | ||||
| 	s := string(rs) | ||||
| 	switch s { | ||||
| 	case "true", "false": | ||||
| 		lx.emit(itemBool) | ||||
| 		return lx.pop() | ||||
| 	} | ||||
| 	return lx.errorf("expected value but found %q instead", s) | ||||
| } | ||||
| 
 | ||||
| // lexCommentStart begins the lexing of a comment. It will emit
 | ||||
| // itemCommentStart and consume no characters, passing control to lexComment.
 | ||||
| func lexCommentStart(lx *lexer) stateFn { | ||||
| 	lx.ignore() | ||||
| 	lx.emit(itemCommentStart) | ||||
| 	return lexComment | ||||
| } | ||||
| 
 | ||||
| // lexComment lexes an entire comment. It assumes that '#' has been consumed.
 | ||||
| // It will consume *up to* the first newline character, and pass control
 | ||||
| // back to the last state on the stack.
 | ||||
| func lexComment(lx *lexer) stateFn { | ||||
| 	r := lx.peek() | ||||
| 	if isNL(r) || r == eof { | ||||
| 		lx.emit(itemText) | ||||
| 		return lx.pop() | ||||
| 	} | ||||
| 	lx.next() | ||||
| 	return lexComment | ||||
| } | ||||
| 
 | ||||
| // lexSkip ignores all slurped input and moves on to the next state.
 | ||||
| func lexSkip(lx *lexer, nextState stateFn) stateFn { | ||||
| 	return func(lx *lexer) stateFn { | ||||
| 		lx.ignore() | ||||
| 		return nextState | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // isWhitespace returns true if `r` is a whitespace character according
 | ||||
| // to the spec.
 | ||||
| func isWhitespace(r rune) bool { | ||||
| 	return r == '\t' || r == ' ' | ||||
| } | ||||
| 
 | ||||
| func isNL(r rune) bool { | ||||
| 	return r == '\n' || r == '\r' | ||||
| } | ||||
| 
 | ||||
| func isDigit(r rune) bool { | ||||
| 	return r >= '0' && r <= '9' | ||||
| } | ||||
| 
 | ||||
| func isHexadecimal(r rune) bool { | ||||
| 	return (r >= '0' && r <= '9') || | ||||
| 		(r >= 'a' && r <= 'f') || | ||||
| 		(r >= 'A' && r <= 'F') | ||||
| } | ||||
| 
 | ||||
| func isBareKeyChar(r rune) bool { | ||||
| 	return (r >= 'A' && r <= 'Z') || | ||||
| 		(r >= 'a' && r <= 'z') || | ||||
| 		(r >= '0' && r <= '9') || | ||||
| 		r == '_' || | ||||
| 		r == '-' | ||||
| } | ||||
| 
 | ||||
| func (itype itemType) String() string { | ||||
| 	switch itype { | ||||
| 	case itemError: | ||||
| 		return "Error" | ||||
| 	case itemNIL: | ||||
| 		return "NIL" | ||||
| 	case itemEOF: | ||||
| 		return "EOF" | ||||
| 	case itemText: | ||||
| 		return "Text" | ||||
| 	case itemString, itemRawString, itemMultilineString, itemRawMultilineString: | ||||
| 		return "String" | ||||
| 	case itemBool: | ||||
| 		return "Bool" | ||||
| 	case itemInteger: | ||||
| 		return "Integer" | ||||
| 	case itemFloat: | ||||
| 		return "Float" | ||||
| 	case itemDatetime: | ||||
| 		return "DateTime" | ||||
| 	case itemTableStart: | ||||
| 		return "TableStart" | ||||
| 	case itemTableEnd: | ||||
| 		return "TableEnd" | ||||
| 	case itemKeyStart: | ||||
| 		return "KeyStart" | ||||
| 	case itemArray: | ||||
| 		return "Array" | ||||
| 	case itemArrayEnd: | ||||
| 		return "ArrayEnd" | ||||
| 	case itemCommentStart: | ||||
| 		return "CommentStart" | ||||
| 	} | ||||
| 	panic(fmt.Sprintf("BUG: Unknown type '%d'.", int(itype))) | ||||
| } | ||||
| 
 | ||||
| func (item item) String() string { | ||||
| 	return fmt.Sprintf("(%s, %s)", item.typ.String(), item.val) | ||||
| } | ||||
							
								
								
									
										592
									
								
								vendor/github.com/BurntSushi/toml/parse.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										592
									
								
								vendor/github.com/BurntSushi/toml/parse.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,592 +0,0 @@ | ||||
| package toml | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 	"unicode" | ||||
| 	"unicode/utf8" | ||||
| ) | ||||
| 
 | ||||
| type parser struct { | ||||
| 	mapping map[string]interface{} | ||||
| 	types   map[string]tomlType | ||||
| 	lx      *lexer | ||||
| 
 | ||||
| 	// A list of keys in the order that they appear in the TOML data.
 | ||||
| 	ordered []Key | ||||
| 
 | ||||
| 	// the full key for the current hash in scope
 | ||||
| 	context Key | ||||
| 
 | ||||
| 	// the base key name for everything except hashes
 | ||||
| 	currentKey string | ||||
| 
 | ||||
| 	// rough approximation of line number
 | ||||
| 	approxLine int | ||||
| 
 | ||||
| 	// A map of 'key.group.names' to whether they were created implicitly.
 | ||||
| 	implicits map[string]bool | ||||
| } | ||||
| 
 | ||||
| type parseError string | ||||
| 
 | ||||
| func (pe parseError) Error() string { | ||||
| 	return string(pe) | ||||
| } | ||||
| 
 | ||||
| func parse(data string) (p *parser, err error) { | ||||
| 	defer func() { | ||||
| 		if r := recover(); r != nil { | ||||
| 			var ok bool | ||||
| 			if err, ok = r.(parseError); ok { | ||||
| 				return | ||||
| 			} | ||||
| 			panic(r) | ||||
| 		} | ||||
| 	}() | ||||
| 
 | ||||
| 	p = &parser{ | ||||
| 		mapping:   make(map[string]interface{}), | ||||
| 		types:     make(map[string]tomlType), | ||||
| 		lx:        lex(data), | ||||
| 		ordered:   make([]Key, 0), | ||||
| 		implicits: make(map[string]bool), | ||||
| 	} | ||||
| 	for { | ||||
| 		item := p.next() | ||||
| 		if item.typ == itemEOF { | ||||
| 			break | ||||
| 		} | ||||
| 		p.topLevel(item) | ||||
| 	} | ||||
| 
 | ||||
| 	return p, nil | ||||
| } | ||||
| 
 | ||||
| func (p *parser) panicf(format string, v ...interface{}) { | ||||
| 	msg := fmt.Sprintf("Near line %d (last key parsed '%s'): %s", | ||||
| 		p.approxLine, p.current(), fmt.Sprintf(format, v...)) | ||||
| 	panic(parseError(msg)) | ||||
| } | ||||
| 
 | ||||
| func (p *parser) next() item { | ||||
| 	it := p.lx.nextItem() | ||||
| 	if it.typ == itemError { | ||||
| 		p.panicf("%s", it.val) | ||||
| 	} | ||||
| 	return it | ||||
| } | ||||
| 
 | ||||
| func (p *parser) bug(format string, v ...interface{}) { | ||||
| 	panic(fmt.Sprintf("BUG: "+format+"\n\n", v...)) | ||||
| } | ||||
| 
 | ||||
| func (p *parser) expect(typ itemType) item { | ||||
| 	it := p.next() | ||||
| 	p.assertEqual(typ, it.typ) | ||||
| 	return it | ||||
| } | ||||
| 
 | ||||
| func (p *parser) assertEqual(expected, got itemType) { | ||||
| 	if expected != got { | ||||
| 		p.bug("Expected '%s' but got '%s'.", expected, got) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (p *parser) topLevel(item item) { | ||||
| 	switch item.typ { | ||||
| 	case itemCommentStart: | ||||
| 		p.approxLine = item.line | ||||
| 		p.expect(itemText) | ||||
| 	case itemTableStart: | ||||
| 		kg := p.next() | ||||
| 		p.approxLine = kg.line | ||||
| 
 | ||||
| 		var key Key | ||||
| 		for ; kg.typ != itemTableEnd && kg.typ != itemEOF; kg = p.next() { | ||||
| 			key = append(key, p.keyString(kg)) | ||||
| 		} | ||||
| 		p.assertEqual(itemTableEnd, kg.typ) | ||||
| 
 | ||||
| 		p.establishContext(key, false) | ||||
| 		p.setType("", tomlHash) | ||||
| 		p.ordered = append(p.ordered, key) | ||||
| 	case itemArrayTableStart: | ||||
| 		kg := p.next() | ||||
| 		p.approxLine = kg.line | ||||
| 
 | ||||
| 		var key Key | ||||
| 		for ; kg.typ != itemArrayTableEnd && kg.typ != itemEOF; kg = p.next() { | ||||
| 			key = append(key, p.keyString(kg)) | ||||
| 		} | ||||
| 		p.assertEqual(itemArrayTableEnd, kg.typ) | ||||
| 
 | ||||
| 		p.establishContext(key, true) | ||||
| 		p.setType("", tomlArrayHash) | ||||
| 		p.ordered = append(p.ordered, key) | ||||
| 	case itemKeyStart: | ||||
| 		kname := p.next() | ||||
| 		p.approxLine = kname.line | ||||
| 		p.currentKey = p.keyString(kname) | ||||
| 
 | ||||
| 		val, typ := p.value(p.next()) | ||||
| 		p.setValue(p.currentKey, val) | ||||
| 		p.setType(p.currentKey, typ) | ||||
| 		p.ordered = append(p.ordered, p.context.add(p.currentKey)) | ||||
| 		p.currentKey = "" | ||||
| 	default: | ||||
| 		p.bug("Unexpected type at top level: %s", item.typ) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Gets a string for a key (or part of a key in a table name).
 | ||||
| func (p *parser) keyString(it item) string { | ||||
| 	switch it.typ { | ||||
| 	case itemText: | ||||
| 		return it.val | ||||
| 	case itemString, itemMultilineString, | ||||
| 		itemRawString, itemRawMultilineString: | ||||
| 		s, _ := p.value(it) | ||||
| 		return s.(string) | ||||
| 	default: | ||||
| 		p.bug("Unexpected key type: %s", it.typ) | ||||
| 		panic("unreachable") | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // value translates an expected value from the lexer into a Go value wrapped
 | ||||
| // as an empty interface.
 | ||||
| func (p *parser) value(it item) (interface{}, tomlType) { | ||||
| 	switch it.typ { | ||||
| 	case itemString: | ||||
| 		return p.replaceEscapes(it.val), p.typeOfPrimitive(it) | ||||
| 	case itemMultilineString: | ||||
| 		trimmed := stripFirstNewline(stripEscapedWhitespace(it.val)) | ||||
| 		return p.replaceEscapes(trimmed), p.typeOfPrimitive(it) | ||||
| 	case itemRawString: | ||||
| 		return it.val, p.typeOfPrimitive(it) | ||||
| 	case itemRawMultilineString: | ||||
| 		return stripFirstNewline(it.val), p.typeOfPrimitive(it) | ||||
| 	case itemBool: | ||||
| 		switch it.val { | ||||
| 		case "true": | ||||
| 			return true, p.typeOfPrimitive(it) | ||||
| 		case "false": | ||||
| 			return false, p.typeOfPrimitive(it) | ||||
| 		} | ||||
| 		p.bug("Expected boolean value, but got '%s'.", it.val) | ||||
| 	case itemInteger: | ||||
| 		if !numUnderscoresOK(it.val) { | ||||
| 			p.panicf("Invalid integer %q: underscores must be surrounded by digits", | ||||
| 				it.val) | ||||
| 		} | ||||
| 		val := strings.Replace(it.val, "_", "", -1) | ||||
| 		num, err := strconv.ParseInt(val, 10, 64) | ||||
| 		if err != nil { | ||||
| 			// Distinguish integer values. Normally, it'd be a bug if the lexer
 | ||||
| 			// provides an invalid integer, but it's possible that the number is
 | ||||
| 			// out of range of valid values (which the lexer cannot determine).
 | ||||
| 			// So mark the former as a bug but the latter as a legitimate user
 | ||||
| 			// error.
 | ||||
| 			if e, ok := err.(*strconv.NumError); ok && | ||||
| 				e.Err == strconv.ErrRange { | ||||
| 
 | ||||
| 				p.panicf("Integer '%s' is out of the range of 64-bit "+ | ||||
| 					"signed integers.", it.val) | ||||
| 			} else { | ||||
| 				p.bug("Expected integer value, but got '%s'.", it.val) | ||||
| 			} | ||||
| 		} | ||||
| 		return num, p.typeOfPrimitive(it) | ||||
| 	case itemFloat: | ||||
| 		parts := strings.FieldsFunc(it.val, func(r rune) bool { | ||||
| 			switch r { | ||||
| 			case '.', 'e', 'E': | ||||
| 				return true | ||||
| 			} | ||||
| 			return false | ||||
| 		}) | ||||
| 		for _, part := range parts { | ||||
| 			if !numUnderscoresOK(part) { | ||||
| 				p.panicf("Invalid float %q: underscores must be "+ | ||||
| 					"surrounded by digits", it.val) | ||||
| 			} | ||||
| 		} | ||||
| 		if !numPeriodsOK(it.val) { | ||||
| 			// As a special case, numbers like '123.' or '1.e2',
 | ||||
| 			// which are valid as far as Go/strconv are concerned,
 | ||||
| 			// must be rejected because TOML says that a fractional
 | ||||
| 			// part consists of '.' followed by 1+ digits.
 | ||||
| 			p.panicf("Invalid float %q: '.' must be followed "+ | ||||
| 				"by one or more digits", it.val) | ||||
| 		} | ||||
| 		val := strings.Replace(it.val, "_", "", -1) | ||||
| 		num, err := strconv.ParseFloat(val, 64) | ||||
| 		if err != nil { | ||||
| 			if e, ok := err.(*strconv.NumError); ok && | ||||
| 				e.Err == strconv.ErrRange { | ||||
| 
 | ||||
| 				p.panicf("Float '%s' is out of the range of 64-bit "+ | ||||
| 					"IEEE-754 floating-point numbers.", it.val) | ||||
| 			} else { | ||||
| 				p.panicf("Invalid float value: %q", it.val) | ||||
| 			} | ||||
| 		} | ||||
| 		return num, p.typeOfPrimitive(it) | ||||
| 	case itemDatetime: | ||||
| 		var t time.Time | ||||
| 		var ok bool | ||||
| 		var err error | ||||
| 		for _, format := range []string{ | ||||
| 			"2006-01-02T15:04:05Z07:00", | ||||
| 			"2006-01-02T15:04:05", | ||||
| 			"2006-01-02", | ||||
| 		} { | ||||
| 			t, err = time.ParseInLocation(format, it.val, time.Local) | ||||
| 			if err == nil { | ||||
| 				ok = true | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		if !ok { | ||||
| 			p.panicf("Invalid TOML Datetime: %q.", it.val) | ||||
| 		} | ||||
| 		return t, p.typeOfPrimitive(it) | ||||
| 	case itemArray: | ||||
| 		array := make([]interface{}, 0) | ||||
| 		types := make([]tomlType, 0) | ||||
| 
 | ||||
| 		for it = p.next(); it.typ != itemArrayEnd; it = p.next() { | ||||
| 			if it.typ == itemCommentStart { | ||||
| 				p.expect(itemText) | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			val, typ := p.value(it) | ||||
| 			array = append(array, val) | ||||
| 			types = append(types, typ) | ||||
| 		} | ||||
| 		return array, p.typeOfArray(types) | ||||
| 	case itemInlineTableStart: | ||||
| 		var ( | ||||
| 			hash         = make(map[string]interface{}) | ||||
| 			outerContext = p.context | ||||
| 			outerKey     = p.currentKey | ||||
| 		) | ||||
| 
 | ||||
| 		p.context = append(p.context, p.currentKey) | ||||
| 		p.currentKey = "" | ||||
| 		for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() { | ||||
| 			if it.typ != itemKeyStart { | ||||
| 				p.bug("Expected key start but instead found %q, around line %d", | ||||
| 					it.val, p.approxLine) | ||||
| 			} | ||||
| 			if it.typ == itemCommentStart { | ||||
| 				p.expect(itemText) | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			// retrieve key
 | ||||
| 			k := p.next() | ||||
| 			p.approxLine = k.line | ||||
| 			kname := p.keyString(k) | ||||
| 
 | ||||
| 			// retrieve value
 | ||||
| 			p.currentKey = kname | ||||
| 			val, typ := p.value(p.next()) | ||||
| 			// make sure we keep metadata up to date
 | ||||
| 			p.setType(kname, typ) | ||||
| 			p.ordered = append(p.ordered, p.context.add(p.currentKey)) | ||||
| 			hash[kname] = val | ||||
| 		} | ||||
| 		p.context = outerContext | ||||
| 		p.currentKey = outerKey | ||||
| 		return hash, tomlHash | ||||
| 	} | ||||
| 	p.bug("Unexpected value type: %s", it.typ) | ||||
| 	panic("unreachable") | ||||
| } | ||||
| 
 | ||||
| // numUnderscoresOK checks whether each underscore in s is surrounded by
 | ||||
| // characters that are not underscores.
 | ||||
| func numUnderscoresOK(s string) bool { | ||||
| 	accept := false | ||||
| 	for _, r := range s { | ||||
| 		if r == '_' { | ||||
| 			if !accept { | ||||
| 				return false | ||||
| 			} | ||||
| 			accept = false | ||||
| 			continue | ||||
| 		} | ||||
| 		accept = true | ||||
| 	} | ||||
| 	return accept | ||||
| } | ||||
| 
 | ||||
| // numPeriodsOK checks whether every period in s is followed by a digit.
 | ||||
| func numPeriodsOK(s string) bool { | ||||
| 	period := false | ||||
| 	for _, r := range s { | ||||
| 		if period && !isDigit(r) { | ||||
| 			return false | ||||
| 		} | ||||
| 		period = r == '.' | ||||
| 	} | ||||
| 	return !period | ||||
| } | ||||
| 
 | ||||
| // establishContext sets the current context of the parser,
 | ||||
| // where the context is either a hash or an array of hashes. Which one is
 | ||||
| // set depends on the value of the `array` parameter.
 | ||||
| //
 | ||||
| // Establishing the context also makes sure that the key isn't a duplicate, and
 | ||||
| // will create implicit hashes automatically.
 | ||||
| func (p *parser) establishContext(key Key, array bool) { | ||||
| 	var ok bool | ||||
| 
 | ||||
| 	// Always start at the top level and drill down for our context.
 | ||||
| 	hashContext := p.mapping | ||||
| 	keyContext := make(Key, 0) | ||||
| 
 | ||||
| 	// We only need implicit hashes for key[0:-1]
 | ||||
| 	for _, k := range key[0 : len(key)-1] { | ||||
| 		_, ok = hashContext[k] | ||||
| 		keyContext = append(keyContext, k) | ||||
| 
 | ||||
| 		// No key? Make an implicit hash and move on.
 | ||||
| 		if !ok { | ||||
| 			p.addImplicit(keyContext) | ||||
| 			hashContext[k] = make(map[string]interface{}) | ||||
| 		} | ||||
| 
 | ||||
| 		// If the hash context is actually an array of tables, then set
 | ||||
| 		// the hash context to the last element in that array.
 | ||||
| 		//
 | ||||
| 		// Otherwise, it better be a table, since this MUST be a key group (by
 | ||||
| 		// virtue of it not being the last element in a key).
 | ||||
| 		switch t := hashContext[k].(type) { | ||||
| 		case []map[string]interface{}: | ||||
| 			hashContext = t[len(t)-1] | ||||
| 		case map[string]interface{}: | ||||
| 			hashContext = t | ||||
| 		default: | ||||
| 			p.panicf("Key '%s' was already created as a hash.", keyContext) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	p.context = keyContext | ||||
| 	if array { | ||||
| 		// If this is the first element for this array, then allocate a new
 | ||||
| 		// list of tables for it.
 | ||||
| 		k := key[len(key)-1] | ||||
| 		if _, ok := hashContext[k]; !ok { | ||||
| 			hashContext[k] = make([]map[string]interface{}, 0, 5) | ||||
| 		} | ||||
| 
 | ||||
| 		// Add a new table. But make sure the key hasn't already been used
 | ||||
| 		// for something else.
 | ||||
| 		if hash, ok := hashContext[k].([]map[string]interface{}); ok { | ||||
| 			hashContext[k] = append(hash, make(map[string]interface{})) | ||||
| 		} else { | ||||
| 			p.panicf("Key '%s' was already created and cannot be used as "+ | ||||
| 				"an array.", keyContext) | ||||
| 		} | ||||
| 	} else { | ||||
| 		p.setValue(key[len(key)-1], make(map[string]interface{})) | ||||
| 	} | ||||
| 	p.context = append(p.context, key[len(key)-1]) | ||||
| } | ||||
| 
 | ||||
| // setValue sets the given key to the given value in the current context.
 | ||||
| // It will make sure that the key hasn't already been defined, account for
 | ||||
| // implicit key groups.
 | ||||
| func (p *parser) setValue(key string, value interface{}) { | ||||
| 	var tmpHash interface{} | ||||
| 	var ok bool | ||||
| 
 | ||||
| 	hash := p.mapping | ||||
| 	keyContext := make(Key, 0) | ||||
| 	for _, k := range p.context { | ||||
| 		keyContext = append(keyContext, k) | ||||
| 		if tmpHash, ok = hash[k]; !ok { | ||||
| 			p.bug("Context for key '%s' has not been established.", keyContext) | ||||
| 		} | ||||
| 		switch t := tmpHash.(type) { | ||||
| 		case []map[string]interface{}: | ||||
| 			// The context is a table of hashes. Pick the most recent table
 | ||||
| 			// defined as the current hash.
 | ||||
| 			hash = t[len(t)-1] | ||||
| 		case map[string]interface{}: | ||||
| 			hash = t | ||||
| 		default: | ||||
| 			p.bug("Expected hash to have type 'map[string]interface{}', but "+ | ||||
| 				"it has '%T' instead.", tmpHash) | ||||
| 		} | ||||
| 	} | ||||
| 	keyContext = append(keyContext, key) | ||||
| 
 | ||||
| 	if _, ok := hash[key]; ok { | ||||
| 		// Typically, if the given key has already been set, then we have
 | ||||
| 		// to raise an error since duplicate keys are disallowed. However,
 | ||||
| 		// it's possible that a key was previously defined implicitly. In this
 | ||||
| 		// case, it is allowed to be redefined concretely. (See the
 | ||||
| 		// `tests/valid/implicit-and-explicit-after.toml` test in `toml-test`.)
 | ||||
| 		//
 | ||||
| 		// But we have to make sure to stop marking it as an implicit. (So that
 | ||||
| 		// another redefinition provokes an error.)
 | ||||
| 		//
 | ||||
| 		// Note that since it has already been defined (as a hash), we don't
 | ||||
| 		// want to overwrite it. So our business is done.
 | ||||
| 		if p.isImplicit(keyContext) { | ||||
| 			p.removeImplicit(keyContext) | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		// Otherwise, we have a concrete key trying to override a previous
 | ||||
| 		// key, which is *always* wrong.
 | ||||
| 		p.panicf("Key '%s' has already been defined.", keyContext) | ||||
| 	} | ||||
| 	hash[key] = value | ||||
| } | ||||
| 
 | ||||
| // setType sets the type of a particular value at a given key.
 | ||||
| // It should be called immediately AFTER setValue.
 | ||||
| //
 | ||||
| // 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).
 | ||||
| func (p *parser) setType(key string, typ tomlType) { | ||||
| 	keyContext := make(Key, 0, len(p.context)+1) | ||||
| 	for _, k := range p.context { | ||||
| 		keyContext = append(keyContext, k) | ||||
| 	} | ||||
| 	if len(key) > 0 { // allow type setting for hashes
 | ||||
| 		keyContext = append(keyContext, key) | ||||
| 	} | ||||
| 	p.types[keyContext.String()] = typ | ||||
| } | ||||
| 
 | ||||
| // addImplicit sets the given Key as having been created implicitly.
 | ||||
| func (p *parser) addImplicit(key Key) { | ||||
| 	p.implicits[key.String()] = true | ||||
| } | ||||
| 
 | ||||
| // removeImplicit stops tagging the given key as having been implicitly
 | ||||
| // created.
 | ||||
| func (p *parser) removeImplicit(key Key) { | ||||
| 	p.implicits[key.String()] = false | ||||
| } | ||||
| 
 | ||||
| // isImplicit returns true if the key group pointed to by the key was created
 | ||||
| // implicitly.
 | ||||
| func (p *parser) isImplicit(key Key) bool { | ||||
| 	return p.implicits[key.String()] | ||||
| } | ||||
| 
 | ||||
| // current returns the full key name of the current context.
 | ||||
| func (p *parser) current() string { | ||||
| 	if len(p.currentKey) == 0 { | ||||
| 		return p.context.String() | ||||
| 	} | ||||
| 	if len(p.context) == 0 { | ||||
| 		return p.currentKey | ||||
| 	} | ||||
| 	return fmt.Sprintf("%s.%s", p.context, p.currentKey) | ||||
| } | ||||
| 
 | ||||
| func stripFirstNewline(s string) string { | ||||
| 	if len(s) == 0 || s[0] != '\n' { | ||||
| 		return s | ||||
| 	} | ||||
| 	return s[1:] | ||||
| } | ||||
| 
 | ||||
| func stripEscapedWhitespace(s string) string { | ||||
| 	esc := strings.Split(s, "\\\n") | ||||
| 	if len(esc) > 1 { | ||||
| 		for i := 1; i < len(esc); i++ { | ||||
| 			esc[i] = strings.TrimLeftFunc(esc[i], unicode.IsSpace) | ||||
| 		} | ||||
| 	} | ||||
| 	return strings.Join(esc, "") | ||||
| } | ||||
| 
 | ||||
| func (p *parser) replaceEscapes(str string) string { | ||||
| 	var replaced []rune | ||||
| 	s := []byte(str) | ||||
| 	r := 0 | ||||
| 	for r < len(s) { | ||||
| 		if s[r] != '\\' { | ||||
| 			c, size := utf8.DecodeRune(s[r:]) | ||||
| 			r += size | ||||
| 			replaced = append(replaced, c) | ||||
| 			continue | ||||
| 		} | ||||
| 		r += 1 | ||||
| 		if r >= len(s) { | ||||
| 			p.bug("Escape sequence at end of string.") | ||||
| 			return "" | ||||
| 		} | ||||
| 		switch s[r] { | ||||
| 		default: | ||||
| 			p.bug("Expected valid escape code after \\, but got %q.", s[r]) | ||||
| 			return "" | ||||
| 		case 'b': | ||||
| 			replaced = append(replaced, rune(0x0008)) | ||||
| 			r += 1 | ||||
| 		case 't': | ||||
| 			replaced = append(replaced, rune(0x0009)) | ||||
| 			r += 1 | ||||
| 		case 'n': | ||||
| 			replaced = append(replaced, rune(0x000A)) | ||||
| 			r += 1 | ||||
| 		case 'f': | ||||
| 			replaced = append(replaced, rune(0x000C)) | ||||
| 			r += 1 | ||||
| 		case 'r': | ||||
| 			replaced = append(replaced, rune(0x000D)) | ||||
| 			r += 1 | ||||
| 		case '"': | ||||
| 			replaced = append(replaced, rune(0x0022)) | ||||
| 			r += 1 | ||||
| 		case '\\': | ||||
| 			replaced = append(replaced, rune(0x005C)) | ||||
| 			r += 1 | ||||
| 		case 'u': | ||||
| 			// At this point, we know we have a Unicode escape of the form
 | ||||
| 			// `uXXXX` at [r, r+5). (Because the lexer guarantees this
 | ||||
| 			// for us.)
 | ||||
| 			escaped := p.asciiEscapeToUnicode(s[r+1 : r+5]) | ||||
| 			replaced = append(replaced, escaped) | ||||
| 			r += 5 | ||||
| 		case 'U': | ||||
| 			// At this point, we know we have a Unicode escape of the form
 | ||||
| 			// `uXXXX` at [r, r+9). (Because the lexer guarantees this
 | ||||
| 			// for us.)
 | ||||
| 			escaped := p.asciiEscapeToUnicode(s[r+1 : r+9]) | ||||
| 			replaced = append(replaced, escaped) | ||||
| 			r += 9 | ||||
| 		} | ||||
| 	} | ||||
| 	return string(replaced) | ||||
| } | ||||
| 
 | ||||
| func (p *parser) asciiEscapeToUnicode(bs []byte) rune { | ||||
| 	s := string(bs) | ||||
| 	hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32) | ||||
| 	if err != nil { | ||||
| 		p.bug("Could not parse '%s' as a hexadecimal number, but the "+ | ||||
| 			"lexer claims it's OK: %s", s, err) | ||||
| 	} | ||||
| 	if !utf8.ValidRune(rune(hex)) { | ||||
| 		p.panicf("Escaped character '\\u%s' is not valid UTF-8.", s) | ||||
| 	} | ||||
| 	return rune(hex) | ||||
| } | ||||
| 
 | ||||
| func isStringType(ty itemType) bool { | ||||
| 	return ty == itemString || ty == itemMultilineString || | ||||
| 		ty == itemRawString || ty == itemRawMultilineString | ||||
| } | ||||
							
								
								
									
										1
									
								
								vendor/github.com/BurntSushi/toml/session.vim
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								vendor/github.com/BurntSushi/toml/session.vim
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1 +0,0 @@ | ||||
| au BufWritePost *.go silent!make tags > /dev/null 2>&1 | ||||
							
								
								
									
										91
									
								
								vendor/github.com/BurntSushi/toml/type_check.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										91
									
								
								vendor/github.com/BurntSushi/toml/type_check.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,91 +0,0 @@ | ||||
| package toml | ||||
| 
 | ||||
| // tomlType represents any Go type that corresponds to a TOML type.
 | ||||
| // While the first draft of the TOML spec has a simplistic type system that
 | ||||
| // probably doesn't need this level of sophistication, we seem to be militating
 | ||||
| // toward adding real composite types.
 | ||||
| type tomlType interface { | ||||
| 	typeString() string | ||||
| } | ||||
| 
 | ||||
| // typeEqual accepts any two types and returns true if they are equal.
 | ||||
| func typeEqual(t1, t2 tomlType) bool { | ||||
| 	if t1 == nil || t2 == nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	return t1.typeString() == t2.typeString() | ||||
| } | ||||
| 
 | ||||
| func typeIsHash(t tomlType) bool { | ||||
| 	return typeEqual(t, tomlHash) || typeEqual(t, tomlArrayHash) | ||||
| } | ||||
| 
 | ||||
| type tomlBaseType string | ||||
| 
 | ||||
| func (btype tomlBaseType) typeString() string { | ||||
| 	return string(btype) | ||||
| } | ||||
| 
 | ||||
| func (btype tomlBaseType) String() string { | ||||
| 	return btype.typeString() | ||||
| } | ||||
| 
 | ||||
| var ( | ||||
| 	tomlInteger   tomlBaseType = "Integer" | ||||
| 	tomlFloat     tomlBaseType = "Float" | ||||
| 	tomlDatetime  tomlBaseType = "Datetime" | ||||
| 	tomlString    tomlBaseType = "String" | ||||
| 	tomlBool      tomlBaseType = "Bool" | ||||
| 	tomlArray     tomlBaseType = "Array" | ||||
| 	tomlHash      tomlBaseType = "Hash" | ||||
| 	tomlArrayHash tomlBaseType = "ArrayHash" | ||||
| ) | ||||
| 
 | ||||
| // typeOfPrimitive returns a tomlType of any primitive value in TOML.
 | ||||
| // Primitive values are: Integer, Float, Datetime, String and Bool.
 | ||||
| //
 | ||||
| // Passing a lexer item other than the following will cause a BUG message
 | ||||
| // to occur: itemString, itemBool, itemInteger, itemFloat, itemDatetime.
 | ||||
| func (p *parser) typeOfPrimitive(lexItem item) tomlType { | ||||
| 	switch lexItem.typ { | ||||
| 	case itemInteger: | ||||
| 		return tomlInteger | ||||
| 	case itemFloat: | ||||
| 		return tomlFloat | ||||
| 	case itemDatetime: | ||||
| 		return tomlDatetime | ||||
| 	case itemString: | ||||
| 		return tomlString | ||||
| 	case itemMultilineString: | ||||
| 		return tomlString | ||||
| 	case itemRawString: | ||||
| 		return tomlString | ||||
| 	case itemRawMultilineString: | ||||
| 		return tomlString | ||||
| 	case itemBool: | ||||
| 		return tomlBool | ||||
| 	} | ||||
| 	p.bug("Cannot infer primitive type of lex item '%s'.", lexItem) | ||||
| 	panic("unreachable") | ||||
| } | ||||
| 
 | ||||
| // typeOfArray returns a tomlType for an array given a list of types of its
 | ||||
| // values.
 | ||||
| //
 | ||||
| // In the current spec, if an array is homogeneous, then its type is always
 | ||||
| // "Array". If the array is not homogeneous, an error is generated.
 | ||||
| func (p *parser) typeOfArray(types []tomlType) tomlType { | ||||
| 	// Empty arrays are cool.
 | ||||
| 	if len(types) == 0 { | ||||
| 		return tomlArray | ||||
| 	} | ||||
| 
 | ||||
| 	theType := types[0] | ||||
| 	for _, t := range types[1:] { | ||||
| 		if !typeEqual(theType, t) { | ||||
| 			p.panicf("Array contains values of type '%s' and '%s', but "+ | ||||
| 				"arrays must be homogeneous.", theType, t) | ||||
| 		} | ||||
| 	} | ||||
| 	return tomlArray | ||||
| } | ||||
							
								
								
									
										242
									
								
								vendor/github.com/BurntSushi/toml/type_fields.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										242
									
								
								vendor/github.com/BurntSushi/toml/type_fields.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,242 +0,0 @@ | ||||
| package toml | ||||
| 
 | ||||
| // Struct field handling is adapted from code in encoding/json:
 | ||||
| //
 | ||||
| // Copyright 2010 The Go Authors.  All rights reserved.
 | ||||
| // Use of this source code is governed by a BSD-style
 | ||||
| // license that can be found in the Go distribution.
 | ||||
| 
 | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"sort" | ||||
| 	"sync" | ||||
| ) | ||||
| 
 | ||||
| // A field represents a single field found in a struct.
 | ||||
| type field struct { | ||||
| 	name  string       // the name of the field (`toml` tag included)
 | ||||
| 	tag   bool         // whether field has a `toml` tag
 | ||||
| 	index []int        // represents the depth of an anonymous field
 | ||||
| 	typ   reflect.Type // the type of the field
 | ||||
| } | ||||
| 
 | ||||
| // byName sorts field by name, breaking ties with depth,
 | ||||
| // then breaking ties with "name came from toml tag", then
 | ||||
| // breaking ties with index sequence.
 | ||||
| type byName []field | ||||
| 
 | ||||
| func (x byName) Len() int { return len(x) } | ||||
| 
 | ||||
| func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } | ||||
| 
 | ||||
| func (x byName) Less(i, j int) bool { | ||||
| 	if x[i].name != x[j].name { | ||||
| 		return x[i].name < x[j].name | ||||
| 	} | ||||
| 	if len(x[i].index) != len(x[j].index) { | ||||
| 		return len(x[i].index) < len(x[j].index) | ||||
| 	} | ||||
| 	if x[i].tag != x[j].tag { | ||||
| 		return x[i].tag | ||||
| 	} | ||||
| 	return byIndex(x).Less(i, j) | ||||
| } | ||||
| 
 | ||||
| // byIndex sorts field by index sequence.
 | ||||
| type byIndex []field | ||||
| 
 | ||||
| func (x byIndex) Len() int { return len(x) } | ||||
| 
 | ||||
| func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } | ||||
| 
 | ||||
| func (x byIndex) Less(i, j int) bool { | ||||
| 	for k, xik := range x[i].index { | ||||
| 		if k >= len(x[j].index) { | ||||
| 			return false | ||||
| 		} | ||||
| 		if xik != x[j].index[k] { | ||||
| 			return xik < x[j].index[k] | ||||
| 		} | ||||
| 	} | ||||
| 	return len(x[i].index) < len(x[j].index) | ||||
| } | ||||
| 
 | ||||
| // typeFields returns a list of fields that TOML should recognize for the given
 | ||||
| // type. The algorithm is breadth-first search over the set of structs to
 | ||||
| // include - the top struct and then any reachable anonymous structs.
 | ||||
| func typeFields(t reflect.Type) []field { | ||||
| 	// Anonymous fields to explore at the current level and the next.
 | ||||
| 	current := []field{} | ||||
| 	next := []field{{typ: t}} | ||||
| 
 | ||||
| 	// Count of queued names for current level and the next.
 | ||||
| 	count := map[reflect.Type]int{} | ||||
| 	nextCount := map[reflect.Type]int{} | ||||
| 
 | ||||
| 	// Types already visited at an earlier level.
 | ||||
| 	visited := map[reflect.Type]bool{} | ||||
| 
 | ||||
| 	// Fields found.
 | ||||
| 	var fields []field | ||||
| 
 | ||||
| 	for len(next) > 0 { | ||||
| 		current, next = next, current[:0] | ||||
| 		count, nextCount = nextCount, map[reflect.Type]int{} | ||||
| 
 | ||||
| 		for _, f := range current { | ||||
| 			if visited[f.typ] { | ||||
| 				continue | ||||
| 			} | ||||
| 			visited[f.typ] = true | ||||
| 
 | ||||
| 			// Scan f.typ for fields to include.
 | ||||
| 			for i := 0; i < f.typ.NumField(); i++ { | ||||
| 				sf := f.typ.Field(i) | ||||
| 				if sf.PkgPath != "" && !sf.Anonymous { // unexported
 | ||||
| 					continue | ||||
| 				} | ||||
| 				opts := getOptions(sf.Tag) | ||||
| 				if opts.skip { | ||||
| 					continue | ||||
| 				} | ||||
| 				index := make([]int, len(f.index)+1) | ||||
| 				copy(index, f.index) | ||||
| 				index[len(f.index)] = i | ||||
| 
 | ||||
| 				ft := sf.Type | ||||
| 				if ft.Name() == "" && ft.Kind() == reflect.Ptr { | ||||
| 					// Follow pointer.
 | ||||
| 					ft = ft.Elem() | ||||
| 				} | ||||
| 
 | ||||
| 				// Record found field and index sequence.
 | ||||
| 				if opts.name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { | ||||
| 					tagged := opts.name != "" | ||||
| 					name := opts.name | ||||
| 					if name == "" { | ||||
| 						name = sf.Name | ||||
| 					} | ||||
| 					fields = append(fields, field{name, tagged, index, ft}) | ||||
| 					if count[f.typ] > 1 { | ||||
| 						// If there were multiple instances, add a second,
 | ||||
| 						// so that the annihilation code will see a duplicate.
 | ||||
| 						// It only cares about the distinction between 1 or 2,
 | ||||
| 						// so don't bother generating any more copies.
 | ||||
| 						fields = append(fields, fields[len(fields)-1]) | ||||
| 					} | ||||
| 					continue | ||||
| 				} | ||||
| 
 | ||||
| 				// Record new anonymous struct to explore in next round.
 | ||||
| 				nextCount[ft]++ | ||||
| 				if nextCount[ft] == 1 { | ||||
| 					f := field{name: ft.Name(), index: index, typ: ft} | ||||
| 					next = append(next, f) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	sort.Sort(byName(fields)) | ||||
| 
 | ||||
| 	// Delete all fields that are hidden by the Go rules for embedded fields,
 | ||||
| 	// except that fields with TOML tags are promoted.
 | ||||
| 
 | ||||
| 	// The fields are sorted in primary order of name, secondary order
 | ||||
| 	// of field index length. Loop over names; for each name, delete
 | ||||
| 	// hidden fields by choosing the one dominant field that survives.
 | ||||
| 	out := fields[:0] | ||||
| 	for advance, i := 0, 0; i < len(fields); i += advance { | ||||
| 		// One iteration per name.
 | ||||
| 		// Find the sequence of fields with the name of this first field.
 | ||||
| 		fi := fields[i] | ||||
| 		name := fi.name | ||||
| 		for advance = 1; i+advance < len(fields); advance++ { | ||||
| 			fj := fields[i+advance] | ||||
| 			if fj.name != name { | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		if advance == 1 { // Only one field with this name
 | ||||
| 			out = append(out, fi) | ||||
| 			continue | ||||
| 		} | ||||
| 		dominant, ok := dominantField(fields[i : i+advance]) | ||||
| 		if ok { | ||||
| 			out = append(out, dominant) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	fields = out | ||||
| 	sort.Sort(byIndex(fields)) | ||||
| 
 | ||||
| 	return fields | ||||
| } | ||||
| 
 | ||||
| // dominantField looks through the fields, all of which are known to
 | ||||
| // have the same name, to find the single field that dominates the
 | ||||
| // others using Go's embedding rules, modified by the presence of
 | ||||
| // TOML tags. If there are multiple top-level fields, the boolean
 | ||||
| // will be false: This condition is an error in Go and we skip all
 | ||||
| // the fields.
 | ||||
| func dominantField(fields []field) (field, bool) { | ||||
| 	// The fields are sorted in increasing index-length order. The winner
 | ||||
| 	// must therefore be one with the shortest index length. Drop all
 | ||||
| 	// longer entries, which is easy: just truncate the slice.
 | ||||
| 	length := len(fields[0].index) | ||||
| 	tagged := -1 // Index of first tagged field.
 | ||||
| 	for i, f := range fields { | ||||
| 		if len(f.index) > length { | ||||
| 			fields = fields[:i] | ||||
| 			break | ||||
| 		} | ||||
| 		if f.tag { | ||||
| 			if tagged >= 0 { | ||||
| 				// Multiple tagged fields at the same level: conflict.
 | ||||
| 				// Return no field.
 | ||||
| 				return field{}, false | ||||
| 			} | ||||
| 			tagged = i | ||||
| 		} | ||||
| 	} | ||||
| 	if tagged >= 0 { | ||||
| 		return fields[tagged], true | ||||
| 	} | ||||
| 	// All remaining fields have the same length. If there's more than one,
 | ||||
| 	// we have a conflict (two fields named "X" at the same level) and we
 | ||||
| 	// return no field.
 | ||||
| 	if len(fields) > 1 { | ||||
| 		return field{}, false | ||||
| 	} | ||||
| 	return fields[0], true | ||||
| } | ||||
| 
 | ||||
| var fieldCache struct { | ||||
| 	sync.RWMutex | ||||
| 	m map[reflect.Type][]field | ||||
| } | ||||
| 
 | ||||
| // cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
 | ||||
| func cachedTypeFields(t reflect.Type) []field { | ||||
| 	fieldCache.RLock() | ||||
| 	f := fieldCache.m[t] | ||||
| 	fieldCache.RUnlock() | ||||
| 	if f != nil { | ||||
| 		return f | ||||
| 	} | ||||
| 
 | ||||
| 	// Compute fields without lock.
 | ||||
| 	// Might duplicate effort but won't hold other computations back.
 | ||||
| 	f = typeFields(t) | ||||
| 	if f == nil { | ||||
| 		f = []field{} | ||||
| 	} | ||||
| 
 | ||||
| 	fieldCache.Lock() | ||||
| 	if fieldCache.m == nil { | ||||
| 		fieldCache.m = map[reflect.Type][]field{} | ||||
| 	} | ||||
| 	fieldCache.m[t] = f | ||||
| 	fieldCache.Unlock() | ||||
| 	return f | ||||
| } | ||||
							
								
								
									
										3
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							| @ -1,3 +0,0 @@ | ||||
| # github.com/BurntSushi/toml v0.3.1 | ||||
| ## explicit | ||||
| github.com/BurntSushi/toml | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user