├── LICENSE ├── README.md ├── _third_party └── github.com │ ├── BurntSushi │ └── toml │ │ ├── COMPATIBLE │ │ ├── COPYING │ │ ├── Makefile │ │ ├── README.md │ │ ├── decode.go │ │ ├── decode_meta.go │ │ ├── decode_test.go │ │ ├── doc.go │ │ ├── encode.go │ │ ├── encode_test.go │ │ ├── encoding_types.go │ │ ├── encoding_types_1.1.go │ │ ├── lex.go │ │ ├── parse.go │ │ ├── session.vim │ │ ├── type_check.go │ │ └── type_fields.go │ └── bradfitz │ └── slice │ ├── COPYING │ ├── LICENSE │ ├── README │ ├── slice.go │ └── slice_test.go ├── cmd └── httpunit │ ├── doc.go │ └── main.go ├── httpunit.go ├── httpunit_test.go └── mk_rpm_fpmdir.httpunit.txt /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Stack Exchange 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # httpUnit 2 | 3 | httpUnit tests web and net servers for basic functionality. 4 | 5 | The tool can open an http/https connection and verify that the expected 6 | status code is received. It can also verify that the resulting HTML output 7 | contains a string or regular expression. It can direct the request to a 8 | particular IP address, ignoring DNS (similar to `curl --resolve`). The tool 9 | can also open a TCP connection and verify that the connection was completed. 10 | For https connections it will output various TLS-related information. 11 | 12 | Tests can be input three ways: 13 | 14 | * *Command line:* A single test can be listed on the command line. This is useful for interactive debugging. 15 | * *TOML file:* A list of tests, with a full range of features, can be listed in a TOML file. This is the recommended mode for tests done on a regular basis. The format is described below. 16 | * *JSON/Hiera mode:* A simple list tcp tests can be specified in JSON format. These are in the format of an iptables "set". This mode is highly specific to a local requirement. 17 | 18 | When specifying a single test on the command line, the only tests performed 19 | are status code, and regex. If the URL does not contain a scheme ("https://", 20 | "http://"), "http://" is prefixed. The IP may be an empty string to indicate 21 | all IP addresses resolved to from the URL's hostname. 22 | 23 | Usage: 24 | 25 | httpUnit [flag] [-hiera="path/to/sets.json"] [-toml="/path/to/httpunit.toml"] [url] [ip] [code] [regex] 26 | 27 | The flags are: 28 | 29 | -filter="" 30 | if specified, only uses this IP address; may end with "." to 31 | filter by IPs that start with the filter 32 | -no10=false 33 | no RFC1918 addresses 34 | -timeout="3s" 35 | connection timeout 36 | -tags="" 37 | if specified, only runs plans that are tagged with one of the 38 | tags specified. You can specify more than one tag, seperated by commas 39 | -protos="" 40 | if specified, only runs plans where the URL contains the given 41 | protocol. Valid protocols are: http,https,tcp,tcp4,tcp6,udp,udp4,udp6,ip,ip4,ip6 42 | You can specify more than one protocol, seperated by commas 43 | -header="X-Request-Guid" 44 | in more verbose mode, print this HTTP header 45 | -v 46 | verbose output: show successes 47 | -vv 48 | more verbose output: show -header, cert details 49 | 50 | ### URLs 51 | 52 | URLs may be specified with various protocols: http, https, tcp, 53 | udp, ip. "4" or "6" may be appended to tcp, udp, and ip (as per 54 | [net/#Dial](http://golang.org/pkg/net/#Dial)). tcp and udp must specify 55 | a port, or default to 0. http and https may specify a port to override 56 | the default. 57 | 58 | ### TOML 59 | 60 | The [toml file](https://github.com/toml-lang/toml) has two sections: 61 | `Plan` is a list of test plans. 62 | `IPs` are a table of search and replace regexes. 63 | 64 | Each `[[plan]]` lists: 65 | 66 | * `label =` A label for documentation purposes. It must be unique. 67 | * `url =` The URL to retrieve. 68 | * `ips =` For http/https, a list of IPs to send the URL to. Default is "use DNS". Otherwise the connection is made to the IP address listed, ignoring DNS. 69 | * `code =` For http/https, the expected status code, default 200. 70 | * `string =` For http/https, a string we expect to find in the result. 71 | * `regex =` For http/https, a regular expression we expect to match in the result. 72 | * `timeout =` An optional timeout for the test in seconds. Default is 3 seconds. 73 | * `tags =` An optional set of tags for the test. Used for when you want to only run a subset of tests with the `-tags` flag 74 | * `insecureSkipVerify = true` Will allow testing of untrusted or self-signed certificates. 75 | 76 | 77 | The test plan is run once for each item in the ips list, or more if macros 78 | are in effect. 79 | 80 | In the `ips` list, `*` will be substituted by all the A and AAAA records 81 | returned when DNS is performed on the hostname of the URL. 82 | 83 | The `[[IPs]]` section is for defining macros. Here are some typical use-cases: 84 | 85 | Specify a value _n_ to mean "10.0.0.n". This may save you a lot of typing 86 | in a big configuration file: 87 | 88 | '^(\d+)$' = ["10.0.0.$1"] 89 | 90 | Similar to the previous example, but specify a base address: 91 | 92 | BASEIP = ["10.0.0."] 93 | '^(\d+)$' = ["BASEIP$1"] 94 | 95 | Specify a value _n_ to mean the 16th IP address in many CIDR bocks: 96 | 97 | '^(\d+)$' = ["10.0.0.$1", "10.1.1.$1", "10.2.2.$1", "10.3.3.$1", "10.4.4.$1"] 98 | 99 | Specify a value _nINT_ to mean .n and .n+64, plus whatever DNS returns: 100 | 101 | BASEIP = ["10.0.0."] 102 | '^(\d+)$' = ["BASEIP$1", "BASEIP($1+64)", "*"] 103 | 104 | ## Why we made this? 105 | 106 | This tool makes it easy to do test-driven development on your web server. 107 | 108 | Every request to our web server passes through three systems that are all 109 | complex and error prone: the firewall, the load balancer, and the web server 110 | itself. Even when running in a test environment with automated tools that 111 | generate the configurations we still needed a way to test our results. 112 | 113 | Before this tool each change was followed by a few simple manual tests, 114 | often by manually typing `curl` commands. We missed a lot of errors. 115 | 116 | With this tool, we now have hundreds of tests that we can run with a single 117 | command. The tests run in parallel therefore all the testing is done very 118 | quickly. 119 | 120 | When we need to make a change we first add the test, then we proceed making 121 | the change until the test passes. This test-driven development has accumulated 122 | 200 tests that we run for any change, considerably more than we'd ever run 123 | manually. This improves confidence in our ability to make changes quickly. 124 | 125 | While making unrelated changes we often run it in a loop to make sure we 126 | don't unintentionally break anything. 127 | 128 | The command-line mode has been useful both in development of changes, and 129 | diagnosing outages. 130 | 131 | ## A simple example file: 132 | 133 | # Verify that this URL returns text that matches "some regex": 134 | [[plan]] 135 | label = "api" 136 | url = "http://api.example.com/" 137 | text = "API for example.com" 138 | regex = "some regex" 139 | 140 | # Verify that this URL returns a redirect. Send to both 141 | # the IP address listed in DNS, plus 10.11.22.33 and 10.99.88.77. 142 | [[plan]] 143 | label = "redirect" 144 | url = "https://example.com/redirect" 145 | ips = ["*", "10.11.22.33", "10.99.88.77"] 146 | code = 301 147 | 148 | ## A more complex example file: 149 | 150 | In this example we want an IP address to mean the IP address, but if we 151 | specify a single number (e.g. "16") we want that to expand to the .16 address 152 | of a few different CIDR blocks. We also want to be able to specify a number + 153 | INT (e.g. "16INT") to indicate a slightly different list. 154 | 155 | [IPs] 156 | BASEIP = ["87.65.43."] 157 | '^(\d+)$' = ["*", "BASEIP$1", "BASEIP($1+64)", "1.2.3.$1"] 158 | '^(\d+)INT$' = ["10.0.1.$1", "10.0.2.$1", "BASEIP$1", "BASEIP($1+64)"] 159 | 160 | [[plan]] 161 | label = "api" 162 | url = "http://api.example.com/" 163 | # This will generate the DNS A/AAAA records, 87.65.43.16, 87.65.43.80, 1.2.3.16 and 8.7.6.5: 164 | ips = ["16", "8.7.6.5"] 165 | text = "API for example.com" 166 | regex = "some regex" 167 | tags = ["apis","example.com"] 168 | 169 | [[plan]] 170 | label = "redirect" 171 | url = "https://example.com/redirect" 172 | # This will generate the DNS A/AAAA records, 10.0.1.20, 10.0.2.20, 87.65.43.20, 87.65.43.84: 173 | ips = ["*", "20INT"] 174 | code = 301 175 | tags = ["redirect","example.com"] 176 | 177 | [[plan]] 178 | label = "mail" 179 | url = "tcp://mail-host.com:25" 180 | -------------------------------------------------------------------------------- /_third_party/github.com/BurntSushi/toml/COMPATIBLE: -------------------------------------------------------------------------------- 1 | Compatible with TOML version 2 | [v0.2.0](https://github.com/mojombo/toml/blob/master/versions/toml-v0.2.0.md) 3 | 4 | -------------------------------------------------------------------------------- /_third_party/github.com/BurntSushi/toml/COPYING: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | 15 | -------------------------------------------------------------------------------- /_third_party/github.com/BurntSushi/toml/Makefile: -------------------------------------------------------------------------------- 1 | install: 2 | go install ./... 3 | 4 | test: install 5 | go test -v 6 | toml-test toml-test-decoder 7 | toml-test -encoder toml-test-encoder 8 | 9 | fmt: 10 | gofmt -w *.go */*.go 11 | colcheck *.go */*.go 12 | 13 | tags: 14 | find ./ -name '*.go' -print0 | xargs -0 gotags > TAGS 15 | 16 | push: 17 | git push origin master 18 | git push github master 19 | 20 | -------------------------------------------------------------------------------- /_third_party/github.com/BurntSushi/toml/README.md: -------------------------------------------------------------------------------- 1 | ## TOML parser and encoder for Go with reflection 2 | 3 | TOML stands for Tom's Obvious, Minimal Language. This Go package provides a 4 | reflection interface similar to Go's standard library `json` and `xml` 5 | packages. This package also supports the `encoding.TextUnmarshaler` and 6 | `encoding.TextMarshaler` interfaces so that you can define custom data 7 | representations. (There is an example of this below.) 8 | 9 | Spec: https://github.com/mojombo/toml 10 | 11 | Compatible with TOML version 12 | [v0.2.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.2.0.md) 13 | 14 | Documentation: http://godoc.org/github.com/BurntSushi/toml 15 | 16 | Installation: 17 | 18 | ```bash 19 | go get github.com/BurntSushi/toml 20 | ``` 21 | 22 | Try the toml validator: 23 | 24 | ```bash 25 | go get github.com/BurntSushi/toml/cmd/tomlv 26 | tomlv some-toml-file.toml 27 | ``` 28 | 29 | [![Build status](https://api.travis-ci.org/BurntSushi/toml.png)](https://travis-ci.org/BurntSushi/toml) 30 | 31 | 32 | ### Testing 33 | 34 | This package passes all tests in 35 | [toml-test](https://github.com/BurntSushi/toml-test) for both the decoder 36 | and the encoder. 37 | 38 | ### Examples 39 | 40 | This package works similarly to how the Go standard library handles `XML` 41 | and `JSON`. Namely, data is loaded into Go values via reflection. 42 | 43 | For the simplest example, consider some TOML file as just a list of keys 44 | and values: 45 | 46 | ```toml 47 | Age = 25 48 | Cats = [ "Cauchy", "Plato" ] 49 | Pi = 3.14 50 | Perfection = [ 6, 28, 496, 8128 ] 51 | DOB = 1987-07-05T05:45:00Z 52 | ``` 53 | 54 | Which could be defined in Go as: 55 | 56 | ```go 57 | type Config struct { 58 | Age int 59 | Cats []string 60 | Pi float64 61 | Perfection []int 62 | DOB time.Time // requires `import time` 63 | } 64 | ``` 65 | 66 | And then decoded with: 67 | 68 | ```go 69 | var conf Config 70 | if _, err := toml.Decode(tomlData, &conf); err != nil { 71 | // handle error 72 | } 73 | ``` 74 | 75 | You can also use struct tags if your struct field name doesn't map to a TOML 76 | key value directly: 77 | 78 | ```toml 79 | some_key_NAME = "wat" 80 | ``` 81 | 82 | ```go 83 | type TOML struct { 84 | ObscureKey string `toml:"some_key_NAME"` 85 | } 86 | ``` 87 | 88 | ### Using the `encoding.TextUnmarshaler` interface 89 | 90 | Here's an example that automatically parses duration strings into 91 | `time.Duration` values: 92 | 93 | ```toml 94 | [[song]] 95 | name = "Thunder Road" 96 | duration = "4m49s" 97 | 98 | [[song]] 99 | name = "Stairway to Heaven" 100 | duration = "8m03s" 101 | ``` 102 | 103 | Which can be decoded with: 104 | 105 | ```go 106 | type song struct { 107 | Name string 108 | Duration duration 109 | } 110 | type songs struct { 111 | Song []song 112 | } 113 | var favorites songs 114 | if _, err := toml.Decode(blob, &favorites); err != nil { 115 | log.Fatal(err) 116 | } 117 | 118 | for _, s := range favorites.Song { 119 | fmt.Printf("%s (%s)\n", s.Name, s.Duration) 120 | } 121 | ``` 122 | 123 | And you'll also need a `duration` type that satisfies the 124 | `encoding.TextUnmarshaler` interface: 125 | 126 | ```go 127 | type duration struct { 128 | time.Duration 129 | } 130 | 131 | func (d *duration) UnmarshalText(text []byte) error { 132 | var err error 133 | d.Duration, err = time.ParseDuration(string(text)) 134 | return err 135 | } 136 | ``` 137 | 138 | ### More complex usage 139 | 140 | Here's an example of how to load the example from the official spec page: 141 | 142 | ```toml 143 | # This is a TOML document. Boom. 144 | 145 | title = "TOML Example" 146 | 147 | [owner] 148 | name = "Tom Preston-Werner" 149 | organization = "GitHub" 150 | bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." 151 | dob = 1979-05-27T07:32:00Z # First class dates? Why not? 152 | 153 | [database] 154 | server = "192.168.1.1" 155 | ports = [ 8001, 8001, 8002 ] 156 | connection_max = 5000 157 | enabled = true 158 | 159 | [servers] 160 | 161 | # You can indent as you please. Tabs or spaces. TOML don't care. 162 | [servers.alpha] 163 | ip = "10.0.0.1" 164 | dc = "eqdc10" 165 | 166 | [servers.beta] 167 | ip = "10.0.0.2" 168 | dc = "eqdc10" 169 | 170 | [clients] 171 | data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it 172 | 173 | # Line breaks are OK when inside arrays 174 | hosts = [ 175 | "alpha", 176 | "omega" 177 | ] 178 | ``` 179 | 180 | And the corresponding Go types are: 181 | 182 | ```go 183 | type tomlConfig struct { 184 | Title string 185 | Owner ownerInfo 186 | DB database `toml:"database"` 187 | Servers map[string]server 188 | Clients clients 189 | } 190 | 191 | type ownerInfo struct { 192 | Name string 193 | Org string `toml:"organization"` 194 | Bio string 195 | DOB time.Time 196 | } 197 | 198 | type database struct { 199 | Server string 200 | Ports []int 201 | ConnMax int `toml:"connection_max"` 202 | Enabled bool 203 | } 204 | 205 | type server struct { 206 | IP string 207 | DC string 208 | } 209 | 210 | type clients struct { 211 | Data [][]interface{} 212 | Hosts []string 213 | } 214 | ``` 215 | 216 | Note that a case insensitive match will be tried if an exact match can't be 217 | found. 218 | 219 | A working example of the above can be found in `_examples/example.{go,toml}`. 220 | 221 | -------------------------------------------------------------------------------- /_third_party/github.com/BurntSushi/toml/decode.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "math" 8 | "reflect" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | var e = fmt.Errorf 14 | 15 | // Unmarshaler is the interface implemented by objects that can unmarshal a 16 | // TOML description of themselves. 17 | type Unmarshaler interface { 18 | UnmarshalTOML(interface{}) error 19 | } 20 | 21 | // Unmarshal decodes the contents of `p` in TOML format into a pointer `v`. 22 | func Unmarshal(p []byte, v interface{}) error { 23 | _, err := Decode(string(p), v) 24 | return err 25 | } 26 | 27 | // Primitive is a TOML value that hasn't been decoded into a Go value. 28 | // When using the various `Decode*` functions, the type `Primitive` may 29 | // be given to any value, and its decoding will be delayed. 30 | // 31 | // A `Primitive` value can be decoded using the `PrimitiveDecode` function. 32 | // 33 | // The underlying representation of a `Primitive` value is subject to change. 34 | // Do not rely on it. 35 | // 36 | // N.B. Primitive values are still parsed, so using them will only avoid 37 | // the overhead of reflection. They can be useful when you don't know the 38 | // exact type of TOML data until run time. 39 | type Primitive struct { 40 | undecoded interface{} 41 | context Key 42 | } 43 | 44 | // DEPRECATED! 45 | // 46 | // Use MetaData.PrimitiveDecode instead. 47 | func PrimitiveDecode(primValue Primitive, v interface{}) error { 48 | md := MetaData{decoded: make(map[string]bool)} 49 | return md.unify(primValue.undecoded, rvalue(v)) 50 | } 51 | 52 | // PrimitiveDecode is just like the other `Decode*` functions, except it 53 | // decodes a TOML value that has already been parsed. Valid primitive values 54 | // can *only* be obtained from values filled by the decoder functions, 55 | // including this method. (i.e., `v` may contain more `Primitive` 56 | // values.) 57 | // 58 | // Meta data for primitive values is included in the meta data returned by 59 | // the `Decode*` functions with one exception: keys returned by the Undecoded 60 | // method will only reflect keys that were decoded. Namely, any keys hidden 61 | // behind a Primitive will be considered undecoded. Executing this method will 62 | // update the undecoded keys in the meta data. (See the example.) 63 | func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error { 64 | md.context = primValue.context 65 | defer func() { md.context = nil }() 66 | return md.unify(primValue.undecoded, rvalue(v)) 67 | } 68 | 69 | // Decode will decode the contents of `data` in TOML format into a pointer 70 | // `v`. 71 | // 72 | // TOML hashes correspond to Go structs or maps. (Dealer's choice. They can be 73 | // used interchangeably.) 74 | // 75 | // TOML arrays of tables correspond to either a slice of structs or a slice 76 | // of maps. 77 | // 78 | // TOML datetimes correspond to Go `time.Time` values. 79 | // 80 | // All other TOML types (float, string, int, bool and array) correspond 81 | // to the obvious Go types. 82 | // 83 | // An exception to the above rules is if a type implements the 84 | // encoding.TextUnmarshaler interface. In this case, any primitive TOML value 85 | // (floats, strings, integers, booleans and datetimes) will be converted to 86 | // a byte string and given to the value's UnmarshalText method. See the 87 | // Unmarshaler example for a demonstration with time duration strings. 88 | // 89 | // Key mapping 90 | // 91 | // TOML keys can map to either keys in a Go map or field names in a Go 92 | // struct. The special `toml` struct tag may be used to map TOML keys to 93 | // struct fields that don't match the key name exactly. (See the example.) 94 | // A case insensitive match to struct names will be tried if an exact match 95 | // can't be found. 96 | // 97 | // The mapping between TOML values and Go values is loose. That is, there 98 | // may exist TOML values that cannot be placed into your representation, and 99 | // there may be parts of your representation that do not correspond to 100 | // TOML values. This loose mapping can be made stricter by using the IsDefined 101 | // and/or Undecoded methods on the MetaData returned. 102 | // 103 | // This decoder will not handle cyclic types. If a cyclic type is passed, 104 | // `Decode` will not terminate. 105 | func Decode(data string, v interface{}) (MetaData, error) { 106 | p, err := parse(data) 107 | if err != nil { 108 | return MetaData{}, err 109 | } 110 | md := MetaData{ 111 | p.mapping, p.types, p.ordered, 112 | make(map[string]bool, len(p.ordered)), nil, 113 | } 114 | return md, md.unify(p.mapping, rvalue(v)) 115 | } 116 | 117 | // DecodeFile is just like Decode, except it will automatically read the 118 | // contents of the file at `fpath` and decode it for you. 119 | func DecodeFile(fpath string, v interface{}) (MetaData, error) { 120 | bs, err := ioutil.ReadFile(fpath) 121 | if err != nil { 122 | return MetaData{}, err 123 | } 124 | return Decode(string(bs), v) 125 | } 126 | 127 | // DecodeReader is just like Decode, except it will consume all bytes 128 | // from the reader and decode it for you. 129 | func DecodeReader(r io.Reader, v interface{}) (MetaData, error) { 130 | bs, err := ioutil.ReadAll(r) 131 | if err != nil { 132 | return MetaData{}, err 133 | } 134 | return Decode(string(bs), v) 135 | } 136 | 137 | // unify performs a sort of type unification based on the structure of `rv`, 138 | // which is the client representation. 139 | // 140 | // Any type mismatch produces an error. Finding a type that we don't know 141 | // how to handle produces an unsupported type error. 142 | func (md *MetaData) unify(data interface{}, rv reflect.Value) error { 143 | 144 | // Special case. Look for a `Primitive` value. 145 | if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() { 146 | // Save the undecoded data and the key context into the primitive 147 | // value. 148 | context := make(Key, len(md.context)) 149 | copy(context, md.context) 150 | rv.Set(reflect.ValueOf(Primitive{ 151 | undecoded: data, 152 | context: context, 153 | })) 154 | return nil 155 | } 156 | 157 | // Special case. Unmarshaler Interface support. 158 | if rv.CanAddr() { 159 | if v, ok := rv.Addr().Interface().(Unmarshaler); ok { 160 | return v.UnmarshalTOML(data) 161 | } 162 | } 163 | 164 | // Special case. Handle time.Time values specifically. 165 | // TODO: Remove this code when we decide to drop support for Go 1.1. 166 | // This isn't necessary in Go 1.2 because time.Time satisfies the encoding 167 | // interfaces. 168 | if rv.Type().AssignableTo(rvalue(time.Time{}).Type()) { 169 | return md.unifyDatetime(data, rv) 170 | } 171 | 172 | // Special case. Look for a value satisfying the TextUnmarshaler interface. 173 | if v, ok := rv.Interface().(TextUnmarshaler); ok { 174 | return md.unifyText(data, v) 175 | } 176 | // BUG(burntsushi) 177 | // The behavior here is incorrect whenever a Go type satisfies the 178 | // encoding.TextUnmarshaler interface but also corresponds to a TOML 179 | // hash or array. In particular, the unmarshaler should only be applied 180 | // to primitive TOML values. But at this point, it will be applied to 181 | // all kinds of values and produce an incorrect error whenever those values 182 | // are hashes or arrays (including arrays of tables). 183 | 184 | k := rv.Kind() 185 | 186 | // laziness 187 | if k >= reflect.Int && k <= reflect.Uint64 { 188 | return md.unifyInt(data, rv) 189 | } 190 | switch k { 191 | case reflect.Ptr: 192 | elem := reflect.New(rv.Type().Elem()) 193 | err := md.unify(data, reflect.Indirect(elem)) 194 | if err != nil { 195 | return err 196 | } 197 | rv.Set(elem) 198 | return nil 199 | case reflect.Struct: 200 | return md.unifyStruct(data, rv) 201 | case reflect.Map: 202 | return md.unifyMap(data, rv) 203 | case reflect.Array: 204 | return md.unifyArray(data, rv) 205 | case reflect.Slice: 206 | return md.unifySlice(data, rv) 207 | case reflect.String: 208 | return md.unifyString(data, rv) 209 | case reflect.Bool: 210 | return md.unifyBool(data, rv) 211 | case reflect.Interface: 212 | // we only support empty interfaces. 213 | if rv.NumMethod() > 0 { 214 | return e("Unsupported type '%s'.", rv.Kind()) 215 | } 216 | return md.unifyAnything(data, rv) 217 | case reflect.Float32: 218 | fallthrough 219 | case reflect.Float64: 220 | return md.unifyFloat64(data, rv) 221 | } 222 | return e("Unsupported type '%s'.", rv.Kind()) 223 | } 224 | 225 | func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error { 226 | tmap, ok := mapping.(map[string]interface{}) 227 | if !ok { 228 | return mismatch(rv, "map", mapping) 229 | } 230 | 231 | for key, datum := range tmap { 232 | var f *field 233 | fields := cachedTypeFields(rv.Type()) 234 | for i := range fields { 235 | ff := &fields[i] 236 | if ff.name == key { 237 | f = ff 238 | break 239 | } 240 | if f == nil && strings.EqualFold(ff.name, key) { 241 | f = ff 242 | } 243 | } 244 | if f != nil { 245 | subv := rv 246 | for _, i := range f.index { 247 | subv = indirect(subv.Field(i)) 248 | } 249 | if isUnifiable(subv) { 250 | md.decoded[md.context.add(key).String()] = true 251 | md.context = append(md.context, key) 252 | if err := md.unify(datum, subv); err != nil { 253 | return e("Type mismatch for '%s.%s': %s", 254 | rv.Type().String(), f.name, err) 255 | } 256 | md.context = md.context[0 : len(md.context)-1] 257 | } else if f.name != "" { 258 | // Bad user! No soup for you! 259 | return e("Field '%s.%s' is unexported, and therefore cannot "+ 260 | "be loaded with reflection.", rv.Type().String(), f.name) 261 | } 262 | } 263 | } 264 | return nil 265 | } 266 | 267 | func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error { 268 | tmap, ok := mapping.(map[string]interface{}) 269 | if !ok { 270 | return badtype("map", mapping) 271 | } 272 | if rv.IsNil() { 273 | rv.Set(reflect.MakeMap(rv.Type())) 274 | } 275 | for k, v := range tmap { 276 | md.decoded[md.context.add(k).String()] = true 277 | md.context = append(md.context, k) 278 | 279 | rvkey := indirect(reflect.New(rv.Type().Key())) 280 | rvval := reflect.Indirect(reflect.New(rv.Type().Elem())) 281 | if err := md.unify(v, rvval); err != nil { 282 | return err 283 | } 284 | md.context = md.context[0 : len(md.context)-1] 285 | 286 | rvkey.SetString(k) 287 | rv.SetMapIndex(rvkey, rvval) 288 | } 289 | return nil 290 | } 291 | 292 | func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error { 293 | datav := reflect.ValueOf(data) 294 | if datav.Kind() != reflect.Slice { 295 | return badtype("slice", data) 296 | } 297 | sliceLen := datav.Len() 298 | if sliceLen != rv.Len() { 299 | return e("expected array length %d; got TOML array of length %d", 300 | rv.Len(), sliceLen) 301 | } 302 | return md.unifySliceArray(datav, rv) 303 | } 304 | 305 | func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error { 306 | datav := reflect.ValueOf(data) 307 | if datav.Kind() != reflect.Slice { 308 | return badtype("slice", data) 309 | } 310 | sliceLen := datav.Len() 311 | if rv.IsNil() { 312 | rv.Set(reflect.MakeSlice(rv.Type(), sliceLen, sliceLen)) 313 | } 314 | return md.unifySliceArray(datav, rv) 315 | } 316 | 317 | func (md *MetaData) unifySliceArray(data, rv reflect.Value) error { 318 | sliceLen := data.Len() 319 | for i := 0; i < sliceLen; i++ { 320 | v := data.Index(i).Interface() 321 | sliceval := indirect(rv.Index(i)) 322 | if err := md.unify(v, sliceval); err != nil { 323 | return err 324 | } 325 | } 326 | return nil 327 | } 328 | 329 | func (md *MetaData) unifyDatetime(data interface{}, rv reflect.Value) error { 330 | if _, ok := data.(time.Time); ok { 331 | rv.Set(reflect.ValueOf(data)) 332 | return nil 333 | } 334 | return badtype("time.Time", data) 335 | } 336 | 337 | func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error { 338 | if s, ok := data.(string); ok { 339 | rv.SetString(s) 340 | return nil 341 | } 342 | return badtype("string", data) 343 | } 344 | 345 | func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error { 346 | if num, ok := data.(float64); ok { 347 | switch rv.Kind() { 348 | case reflect.Float32: 349 | fallthrough 350 | case reflect.Float64: 351 | rv.SetFloat(num) 352 | default: 353 | panic("bug") 354 | } 355 | return nil 356 | } 357 | return badtype("float", data) 358 | } 359 | 360 | func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error { 361 | if num, ok := data.(int64); ok { 362 | if rv.Kind() >= reflect.Int && rv.Kind() <= reflect.Int64 { 363 | switch rv.Kind() { 364 | case reflect.Int, reflect.Int64: 365 | // No bounds checking necessary. 366 | case reflect.Int8: 367 | if num < math.MinInt8 || num > math.MaxInt8 { 368 | return e("Value '%d' is out of range for int8.", num) 369 | } 370 | case reflect.Int16: 371 | if num < math.MinInt16 || num > math.MaxInt16 { 372 | return e("Value '%d' is out of range for int16.", num) 373 | } 374 | case reflect.Int32: 375 | if num < math.MinInt32 || num > math.MaxInt32 { 376 | return e("Value '%d' is out of range for int32.", num) 377 | } 378 | } 379 | rv.SetInt(num) 380 | } else if rv.Kind() >= reflect.Uint && rv.Kind() <= reflect.Uint64 { 381 | unum := uint64(num) 382 | switch rv.Kind() { 383 | case reflect.Uint, reflect.Uint64: 384 | // No bounds checking necessary. 385 | case reflect.Uint8: 386 | if num < 0 || unum > math.MaxUint8 { 387 | return e("Value '%d' is out of range for uint8.", num) 388 | } 389 | case reflect.Uint16: 390 | if num < 0 || unum > math.MaxUint16 { 391 | return e("Value '%d' is out of range for uint16.", num) 392 | } 393 | case reflect.Uint32: 394 | if num < 0 || unum > math.MaxUint32 { 395 | return e("Value '%d' is out of range for uint32.", num) 396 | } 397 | } 398 | rv.SetUint(unum) 399 | } else { 400 | panic("unreachable") 401 | } 402 | return nil 403 | } 404 | return badtype("integer", data) 405 | } 406 | 407 | func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error { 408 | if b, ok := data.(bool); ok { 409 | rv.SetBool(b) 410 | return nil 411 | } 412 | return badtype("boolean", data) 413 | } 414 | 415 | func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error { 416 | rv.Set(reflect.ValueOf(data)) 417 | return nil 418 | } 419 | 420 | func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error { 421 | var s string 422 | switch sdata := data.(type) { 423 | case TextMarshaler: 424 | text, err := sdata.MarshalText() 425 | if err != nil { 426 | return err 427 | } 428 | s = string(text) 429 | case fmt.Stringer: 430 | s = sdata.String() 431 | case string: 432 | s = sdata 433 | case bool: 434 | s = fmt.Sprintf("%v", sdata) 435 | case int64: 436 | s = fmt.Sprintf("%d", sdata) 437 | case float64: 438 | s = fmt.Sprintf("%f", sdata) 439 | default: 440 | return badtype("primitive (string-like)", data) 441 | } 442 | if err := v.UnmarshalText([]byte(s)); err != nil { 443 | return err 444 | } 445 | return nil 446 | } 447 | 448 | // rvalue returns a reflect.Value of `v`. All pointers are resolved. 449 | func rvalue(v interface{}) reflect.Value { 450 | return indirect(reflect.ValueOf(v)) 451 | } 452 | 453 | // indirect returns the value pointed to by a pointer. 454 | // Pointers are followed until the value is not a pointer. 455 | // New values are allocated for each nil pointer. 456 | // 457 | // An exception to this rule is if the value satisfies an interface of 458 | // interest to us (like encoding.TextUnmarshaler). 459 | func indirect(v reflect.Value) reflect.Value { 460 | if v.Kind() != reflect.Ptr { 461 | if v.CanAddr() { 462 | pv := v.Addr() 463 | if _, ok := pv.Interface().(TextUnmarshaler); ok { 464 | return pv 465 | } 466 | } 467 | return v 468 | } 469 | if v.IsNil() { 470 | v.Set(reflect.New(v.Type().Elem())) 471 | } 472 | return indirect(reflect.Indirect(v)) 473 | } 474 | 475 | func isUnifiable(rv reflect.Value) bool { 476 | if rv.CanSet() { 477 | return true 478 | } 479 | if _, ok := rv.Interface().(TextUnmarshaler); ok { 480 | return true 481 | } 482 | return false 483 | } 484 | 485 | func badtype(expected string, data interface{}) error { 486 | return e("Expected %s but found '%T'.", expected, data) 487 | } 488 | 489 | func mismatch(user reflect.Value, expected string, data interface{}) error { 490 | return e("Type mismatch for %s. Expected %s but found '%T'.", 491 | user.Type().String(), expected, data) 492 | } 493 | -------------------------------------------------------------------------------- /_third_party/github.com/BurntSushi/toml/decode_meta.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import "strings" 4 | 5 | // MetaData allows access to meta information about TOML data that may not 6 | // be inferrable via reflection. In particular, whether a key has been defined 7 | // and the TOML type of a key. 8 | type MetaData struct { 9 | mapping map[string]interface{} 10 | types map[string]tomlType 11 | keys []Key 12 | decoded map[string]bool 13 | context Key // Used only during decoding. 14 | } 15 | 16 | // IsDefined returns true if the key given exists in the TOML data. The key 17 | // should be specified hierarchially. e.g., 18 | // 19 | // // access the TOML key 'a.b.c' 20 | // IsDefined("a", "b", "c") 21 | // 22 | // IsDefined will return false if an empty key given. Keys are case sensitive. 23 | func (md *MetaData) IsDefined(key ...string) bool { 24 | if len(key) == 0 { 25 | return false 26 | } 27 | 28 | var hash map[string]interface{} 29 | var ok bool 30 | var hashOrVal interface{} = md.mapping 31 | for _, k := range key { 32 | if hash, ok = hashOrVal.(map[string]interface{}); !ok { 33 | return false 34 | } 35 | if hashOrVal, ok = hash[k]; !ok { 36 | return false 37 | } 38 | } 39 | return true 40 | } 41 | 42 | // Type returns a string representation of the type of the key specified. 43 | // 44 | // Type will return the empty string if given an empty key or a key that 45 | // does not exist. Keys are case sensitive. 46 | func (md *MetaData) Type(key ...string) string { 47 | fullkey := strings.Join(key, ".") 48 | if typ, ok := md.types[fullkey]; ok { 49 | return typ.typeString() 50 | } 51 | return "" 52 | } 53 | 54 | // Key is the type of any TOML key, including key groups. Use (MetaData).Keys 55 | // to get values of this type. 56 | type Key []string 57 | 58 | func (k Key) String() string { 59 | return strings.Join(k, ".") 60 | } 61 | 62 | func (k Key) maybeQuotedAll() string { 63 | var ss []string 64 | for i := range k { 65 | ss = append(ss, k.maybeQuoted(i)) 66 | } 67 | return strings.Join(ss, ".") 68 | } 69 | 70 | func (k Key) maybeQuoted(i int) string { 71 | quote := false 72 | for _, c := range k[i] { 73 | if !isBareKeyChar(c) { 74 | quote = true 75 | break 76 | } 77 | } 78 | if quote { 79 | return "\"" + strings.Replace(k[i], "\"", "\\\"", -1) + "\"" 80 | } else { 81 | return k[i] 82 | } 83 | } 84 | 85 | func (k Key) add(piece string) Key { 86 | newKey := make(Key, len(k)+1) 87 | copy(newKey, k) 88 | newKey[len(k)] = piece 89 | return newKey 90 | } 91 | 92 | // Keys returns a slice of every key in the TOML data, including key groups. 93 | // Each key is itself a slice, where the first element is the top of the 94 | // hierarchy and the last is the most specific. 95 | // 96 | // The list will have the same order as the keys appeared in the TOML data. 97 | // 98 | // All keys returned are non-empty. 99 | func (md *MetaData) Keys() []Key { 100 | return md.keys 101 | } 102 | 103 | // Undecoded returns all keys that have not been decoded in the order in which 104 | // they appear in the original TOML document. 105 | // 106 | // This includes keys that haven't been decoded because of a Primitive value. 107 | // Once the Primitive value is decoded, the keys will be considered decoded. 108 | // 109 | // Also note that decoding into an empty interface will result in no decoding, 110 | // and so no keys will be considered decoded. 111 | // 112 | // In this sense, the Undecoded keys correspond to keys in the TOML document 113 | // that do not have a concrete type in your representation. 114 | func (md *MetaData) Undecoded() []Key { 115 | undecoded := make([]Key, 0, len(md.keys)) 116 | for _, key := range md.keys { 117 | if !md.decoded[key.String()] { 118 | undecoded = append(undecoded, key) 119 | } 120 | } 121 | return undecoded 122 | } 123 | -------------------------------------------------------------------------------- /_third_party/github.com/BurntSushi/toml/decode_test.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "reflect" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func init() { 12 | log.SetFlags(0) 13 | } 14 | 15 | func TestDecodeSimple(t *testing.T) { 16 | var testSimple = ` 17 | age = 250 18 | andrew = "gallant" 19 | kait = "brady" 20 | now = 1987-07-05T05:45:00Z 21 | yesOrNo = true 22 | pi = 3.14 23 | colors = [ 24 | ["red", "green", "blue"], 25 | ["cyan", "magenta", "yellow", "black"], 26 | ] 27 | 28 | [My.Cats] 29 | plato = "cat 1" 30 | cauchy = "cat 2" 31 | ` 32 | 33 | type cats struct { 34 | Plato string 35 | Cauchy string 36 | } 37 | type simple struct { 38 | Age int 39 | Colors [][]string 40 | Pi float64 41 | YesOrNo bool 42 | Now time.Time 43 | Andrew string 44 | Kait string 45 | My map[string]cats 46 | } 47 | 48 | var val simple 49 | _, err := Decode(testSimple, &val) 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | 54 | now, err := time.Parse("2006-01-02T15:04:05", "1987-07-05T05:45:00") 55 | if err != nil { 56 | panic(err) 57 | } 58 | var answer = simple{ 59 | Age: 250, 60 | Andrew: "gallant", 61 | Kait: "brady", 62 | Now: now, 63 | YesOrNo: true, 64 | Pi: 3.14, 65 | Colors: [][]string{ 66 | {"red", "green", "blue"}, 67 | {"cyan", "magenta", "yellow", "black"}, 68 | }, 69 | My: map[string]cats{ 70 | "Cats": {Plato: "cat 1", Cauchy: "cat 2"}, 71 | }, 72 | } 73 | if !reflect.DeepEqual(val, answer) { 74 | t.Fatalf("Expected\n-----\n%#v\n-----\nbut got\n-----\n%#v\n", 75 | answer, val) 76 | } 77 | } 78 | 79 | func TestDecodeEmbedded(t *testing.T) { 80 | type Dog struct{ Name string } 81 | type Age int 82 | 83 | tests := map[string]struct { 84 | input string 85 | decodeInto interface{} 86 | wantDecoded interface{} 87 | }{ 88 | "embedded struct": { 89 | input: `Name = "milton"`, 90 | decodeInto: &struct{ Dog }{}, 91 | wantDecoded: &struct{ Dog }{Dog{"milton"}}, 92 | }, 93 | "embedded non-nil pointer to struct": { 94 | input: `Name = "milton"`, 95 | decodeInto: &struct{ *Dog }{}, 96 | wantDecoded: &struct{ *Dog }{&Dog{"milton"}}, 97 | }, 98 | "embedded nil pointer to struct": { 99 | input: ``, 100 | decodeInto: &struct{ *Dog }{}, 101 | wantDecoded: &struct{ *Dog }{nil}, 102 | }, 103 | "embedded int": { 104 | input: `Age = -5`, 105 | decodeInto: &struct{ Age }{}, 106 | wantDecoded: &struct{ Age }{-5}, 107 | }, 108 | } 109 | 110 | for label, test := range tests { 111 | _, err := Decode(test.input, test.decodeInto) 112 | if err != nil { 113 | t.Fatal(err) 114 | } 115 | if !reflect.DeepEqual(test.wantDecoded, test.decodeInto) { 116 | t.Errorf("%s: want decoded == %+v, got %+v", 117 | label, test.wantDecoded, test.decodeInto) 118 | } 119 | } 120 | } 121 | 122 | func TestTableArrays(t *testing.T) { 123 | var tomlTableArrays = ` 124 | [[albums]] 125 | name = "Born to Run" 126 | 127 | [[albums.songs]] 128 | name = "Jungleland" 129 | 130 | [[albums.songs]] 131 | name = "Meeting Across the River" 132 | 133 | [[albums]] 134 | name = "Born in the USA" 135 | 136 | [[albums.songs]] 137 | name = "Glory Days" 138 | 139 | [[albums.songs]] 140 | name = "Dancing in the Dark" 141 | ` 142 | 143 | type Song struct { 144 | Name string 145 | } 146 | 147 | type Album struct { 148 | Name string 149 | Songs []Song 150 | } 151 | 152 | type Music struct { 153 | Albums []Album 154 | } 155 | 156 | expected := Music{[]Album{ 157 | {"Born to Run", []Song{{"Jungleland"}, {"Meeting Across the River"}}}, 158 | {"Born in the USA", []Song{{"Glory Days"}, {"Dancing in the Dark"}}}, 159 | }} 160 | var got Music 161 | if _, err := Decode(tomlTableArrays, &got); err != nil { 162 | t.Fatal(err) 163 | } 164 | if !reflect.DeepEqual(expected, got) { 165 | t.Fatalf("\n%#v\n!=\n%#v\n", expected, got) 166 | } 167 | } 168 | 169 | // Case insensitive matching tests. 170 | // A bit more comprehensive than needed given the current implementation, 171 | // but implementations change. 172 | // Probably still missing demonstrations of some ugly corner cases regarding 173 | // case insensitive matching and multiple fields. 174 | func TestCase(t *testing.T) { 175 | var caseToml = ` 176 | tOpString = "string" 177 | tOpInt = 1 178 | tOpFloat = 1.1 179 | tOpBool = true 180 | tOpdate = 2006-01-02T15:04:05Z 181 | tOparray = [ "array" ] 182 | Match = "i should be in Match only" 183 | MatcH = "i should be in MatcH only" 184 | once = "just once" 185 | [nEst.eD] 186 | nEstedString = "another string" 187 | ` 188 | 189 | type InsensitiveEd struct { 190 | NestedString string 191 | } 192 | 193 | type InsensitiveNest struct { 194 | Ed InsensitiveEd 195 | } 196 | 197 | type Insensitive struct { 198 | TopString string 199 | TopInt int 200 | TopFloat float64 201 | TopBool bool 202 | TopDate time.Time 203 | TopArray []string 204 | Match string 205 | MatcH string 206 | Once string 207 | OncE string 208 | Nest InsensitiveNest 209 | } 210 | 211 | tme, err := time.Parse(time.RFC3339, time.RFC3339[:len(time.RFC3339)-5]) 212 | if err != nil { 213 | panic(err) 214 | } 215 | expected := Insensitive{ 216 | TopString: "string", 217 | TopInt: 1, 218 | TopFloat: 1.1, 219 | TopBool: true, 220 | TopDate: tme, 221 | TopArray: []string{"array"}, 222 | MatcH: "i should be in MatcH only", 223 | Match: "i should be in Match only", 224 | Once: "just once", 225 | OncE: "", 226 | Nest: InsensitiveNest{ 227 | Ed: InsensitiveEd{NestedString: "another string"}, 228 | }, 229 | } 230 | var got Insensitive 231 | if _, err := Decode(caseToml, &got); err != nil { 232 | t.Fatal(err) 233 | } 234 | if !reflect.DeepEqual(expected, got) { 235 | t.Fatalf("\n%#v\n!=\n%#v\n", expected, got) 236 | } 237 | } 238 | 239 | func TestPointers(t *testing.T) { 240 | type Object struct { 241 | Type string 242 | Description string 243 | } 244 | 245 | type Dict struct { 246 | NamedObject map[string]*Object 247 | BaseObject *Object 248 | Strptr *string 249 | Strptrs []*string 250 | } 251 | s1, s2, s3 := "blah", "abc", "def" 252 | expected := &Dict{ 253 | Strptr: &s1, 254 | Strptrs: []*string{&s2, &s3}, 255 | NamedObject: map[string]*Object{ 256 | "foo": {"FOO", "fooooo!!!"}, 257 | "bar": {"BAR", "ba-ba-ba-ba-barrrr!!!"}, 258 | }, 259 | BaseObject: &Object{"BASE", "da base"}, 260 | } 261 | 262 | ex1 := ` 263 | Strptr = "blah" 264 | Strptrs = ["abc", "def"] 265 | 266 | [NamedObject.foo] 267 | Type = "FOO" 268 | Description = "fooooo!!!" 269 | 270 | [NamedObject.bar] 271 | Type = "BAR" 272 | Description = "ba-ba-ba-ba-barrrr!!!" 273 | 274 | [BaseObject] 275 | Type = "BASE" 276 | Description = "da base" 277 | ` 278 | dict := new(Dict) 279 | _, err := Decode(ex1, dict) 280 | if err != nil { 281 | t.Errorf("Decode error: %v", err) 282 | } 283 | if !reflect.DeepEqual(expected, dict) { 284 | t.Fatalf("\n%#v\n!=\n%#v\n", expected, dict) 285 | } 286 | } 287 | 288 | type sphere struct { 289 | Center [3]float64 290 | Radius float64 291 | } 292 | 293 | func TestDecodeSimpleArray(t *testing.T) { 294 | var s1 sphere 295 | if _, err := Decode(`center = [0.0, 1.5, 0.0]`, &s1); err != nil { 296 | t.Fatal(err) 297 | } 298 | } 299 | 300 | func TestDecodeArrayWrongSize(t *testing.T) { 301 | var s1 sphere 302 | if _, err := Decode(`center = [0.1, 2.3]`, &s1); err == nil { 303 | t.Fatal("Expected array type mismatch error") 304 | } 305 | } 306 | 307 | func TestDecodeLargeIntoSmallInt(t *testing.T) { 308 | type table struct { 309 | Value int8 310 | } 311 | var tab table 312 | if _, err := Decode(`value = 500`, &tab); err == nil { 313 | t.Fatal("Expected integer out-of-bounds error.") 314 | } 315 | } 316 | 317 | func TestDecodeSizedInts(t *testing.T) { 318 | type table struct { 319 | U8 uint8 320 | U16 uint16 321 | U32 uint32 322 | U64 uint64 323 | U uint 324 | I8 int8 325 | I16 int16 326 | I32 int32 327 | I64 int64 328 | I int 329 | } 330 | answer := table{1, 1, 1, 1, 1, -1, -1, -1, -1, -1} 331 | toml := ` 332 | u8 = 1 333 | u16 = 1 334 | u32 = 1 335 | u64 = 1 336 | u = 1 337 | i8 = -1 338 | i16 = -1 339 | i32 = -1 340 | i64 = -1 341 | i = -1 342 | ` 343 | var tab table 344 | if _, err := Decode(toml, &tab); err != nil { 345 | t.Fatal(err.Error()) 346 | } 347 | if answer != tab { 348 | t.Fatalf("Expected %#v but got %#v", answer, tab) 349 | } 350 | } 351 | 352 | func TestUnmarshaler(t *testing.T) { 353 | 354 | var tomlBlob = ` 355 | [dishes.hamboogie] 356 | name = "Hamboogie with fries" 357 | price = 10.99 358 | 359 | [[dishes.hamboogie.ingredients]] 360 | name = "Bread Bun" 361 | 362 | [[dishes.hamboogie.ingredients]] 363 | name = "Lettuce" 364 | 365 | [[dishes.hamboogie.ingredients]] 366 | name = "Real Beef Patty" 367 | 368 | [[dishes.hamboogie.ingredients]] 369 | name = "Tomato" 370 | 371 | [dishes.eggsalad] 372 | name = "Egg Salad with rice" 373 | price = 3.99 374 | 375 | [[dishes.eggsalad.ingredients]] 376 | name = "Egg" 377 | 378 | [[dishes.eggsalad.ingredients]] 379 | name = "Mayo" 380 | 381 | [[dishes.eggsalad.ingredients]] 382 | name = "Rice" 383 | ` 384 | m := &menu{} 385 | if _, err := Decode(tomlBlob, m); err != nil { 386 | log.Fatal(err) 387 | } 388 | 389 | if len(m.Dishes) != 2 { 390 | t.Log("two dishes should be loaded with UnmarshalTOML()") 391 | t.Errorf("expected %d but got %d", 2, len(m.Dishes)) 392 | } 393 | 394 | eggSalad := m.Dishes["eggsalad"] 395 | if _, ok := interface{}(eggSalad).(dish); !ok { 396 | t.Errorf("expected a dish") 397 | } 398 | 399 | if eggSalad.Name != "Egg Salad with rice" { 400 | t.Errorf("expected the dish to be named 'Egg Salad with rice'") 401 | } 402 | 403 | if len(eggSalad.Ingredients) != 3 { 404 | t.Log("dish should be loaded with UnmarshalTOML()") 405 | t.Errorf("expected %d but got %d", 3, len(eggSalad.Ingredients)) 406 | } 407 | 408 | found := false 409 | for _, i := range eggSalad.Ingredients { 410 | if i.Name == "Rice" { 411 | found = true 412 | break 413 | } 414 | } 415 | if !found { 416 | t.Error("Rice was not loaded in UnmarshalTOML()") 417 | } 418 | 419 | // test on a value - must be passed as * 420 | o := menu{} 421 | if _, err := Decode(tomlBlob, &o); err != nil { 422 | log.Fatal(err) 423 | } 424 | 425 | } 426 | 427 | type menu struct { 428 | Dishes map[string]dish 429 | } 430 | 431 | func (m *menu) UnmarshalTOML(p interface{}) error { 432 | m.Dishes = make(map[string]dish) 433 | data, _ := p.(map[string]interface{}) 434 | dishes := data["dishes"].(map[string]interface{}) 435 | for n, v := range dishes { 436 | if d, ok := v.(map[string]interface{}); ok { 437 | nd := dish{} 438 | nd.UnmarshalTOML(d) 439 | m.Dishes[n] = nd 440 | } else { 441 | return fmt.Errorf("not a dish") 442 | } 443 | } 444 | return nil 445 | } 446 | 447 | type dish struct { 448 | Name string 449 | Price float32 450 | Ingredients []ingredient 451 | } 452 | 453 | func (d *dish) UnmarshalTOML(p interface{}) error { 454 | data, _ := p.(map[string]interface{}) 455 | d.Name, _ = data["name"].(string) 456 | d.Price, _ = data["price"].(float32) 457 | ingredients, _ := data["ingredients"].([]map[string]interface{}) 458 | for _, e := range ingredients { 459 | n, _ := interface{}(e).(map[string]interface{}) 460 | name, _ := n["name"].(string) 461 | i := ingredient{name} 462 | d.Ingredients = append(d.Ingredients, i) 463 | } 464 | return nil 465 | } 466 | 467 | type ingredient struct { 468 | Name string 469 | } 470 | 471 | func ExampleMetaData_PrimitiveDecode() { 472 | var md MetaData 473 | var err error 474 | 475 | var tomlBlob = ` 476 | ranking = ["Springsteen", "J Geils"] 477 | 478 | [bands.Springsteen] 479 | started = 1973 480 | albums = ["Greetings", "WIESS", "Born to Run", "Darkness"] 481 | 482 | [bands."J Geils"] 483 | started = 1970 484 | albums = ["The J. Geils Band", "Full House", "Blow Your Face Out"] 485 | ` 486 | 487 | type band struct { 488 | Started int 489 | Albums []string 490 | } 491 | type classics struct { 492 | Ranking []string 493 | Bands map[string]Primitive 494 | } 495 | 496 | // Do the initial decode. Reflection is delayed on Primitive values. 497 | var music classics 498 | if md, err = Decode(tomlBlob, &music); err != nil { 499 | log.Fatal(err) 500 | } 501 | 502 | // MetaData still includes information on Primitive values. 503 | fmt.Printf("Is `bands.Springsteen` defined? %v\n", 504 | md.IsDefined("bands", "Springsteen")) 505 | 506 | // Decode primitive data into Go values. 507 | for _, artist := range music.Ranking { 508 | // A band is a primitive value, so we need to decode it to get a 509 | // real `band` value. 510 | primValue := music.Bands[artist] 511 | 512 | var aBand band 513 | if err = md.PrimitiveDecode(primValue, &aBand); err != nil { 514 | log.Fatal(err) 515 | } 516 | fmt.Printf("%s started in %d.\n", artist, aBand.Started) 517 | } 518 | // Check to see if there were any fields left undecoded. 519 | // Note that this won't be empty before decoding the Primitive value! 520 | fmt.Printf("Undecoded: %q\n", md.Undecoded()) 521 | 522 | // Output: 523 | // Is `bands.Springsteen` defined? true 524 | // Springsteen started in 1973. 525 | // J Geils started in 1970. 526 | // Undecoded: [] 527 | } 528 | 529 | func ExampleDecode() { 530 | var tomlBlob = ` 531 | # Some comments. 532 | [alpha] 533 | ip = "10.0.0.1" 534 | 535 | [alpha.config] 536 | Ports = [ 8001, 8002 ] 537 | Location = "Toronto" 538 | Created = 1987-07-05T05:45:00Z 539 | 540 | [beta] 541 | ip = "10.0.0.2" 542 | 543 | [beta.config] 544 | Ports = [ 9001, 9002 ] 545 | Location = "New Jersey" 546 | Created = 1887-01-05T05:55:00Z 547 | ` 548 | 549 | type serverConfig struct { 550 | Ports []int 551 | Location string 552 | Created time.Time 553 | } 554 | 555 | type server struct { 556 | IP string `toml:"ip,omitempty"` 557 | Config serverConfig `toml:"config"` 558 | } 559 | 560 | type servers map[string]server 561 | 562 | var config servers 563 | if _, err := Decode(tomlBlob, &config); err != nil { 564 | log.Fatal(err) 565 | } 566 | 567 | for _, name := range []string{"alpha", "beta"} { 568 | s := config[name] 569 | fmt.Printf("Server: %s (ip: %s) in %s created on %s\n", 570 | name, s.IP, s.Config.Location, 571 | s.Config.Created.Format("2006-01-02")) 572 | fmt.Printf("Ports: %v\n", s.Config.Ports) 573 | } 574 | 575 | // Output: 576 | // Server: alpha (ip: 10.0.0.1) in Toronto created on 1987-07-05 577 | // Ports: [8001 8002] 578 | // Server: beta (ip: 10.0.0.2) in New Jersey created on 1887-01-05 579 | // Ports: [9001 9002] 580 | } 581 | 582 | type duration struct { 583 | time.Duration 584 | } 585 | 586 | func (d *duration) UnmarshalText(text []byte) error { 587 | var err error 588 | d.Duration, err = time.ParseDuration(string(text)) 589 | return err 590 | } 591 | 592 | // Example Unmarshaler shows how to decode TOML strings into your own 593 | // custom data type. 594 | func Example_unmarshaler() { 595 | blob := ` 596 | [[song]] 597 | name = "Thunder Road" 598 | duration = "4m49s" 599 | 600 | [[song]] 601 | name = "Stairway to Heaven" 602 | duration = "8m03s" 603 | ` 604 | type song struct { 605 | Name string 606 | Duration duration 607 | } 608 | type songs struct { 609 | Song []song 610 | } 611 | var favorites songs 612 | if _, err := Decode(blob, &favorites); err != nil { 613 | log.Fatal(err) 614 | } 615 | 616 | // Code to implement the TextUnmarshaler interface for `duration`: 617 | // 618 | // type duration struct { 619 | // time.Duration 620 | // } 621 | // 622 | // func (d *duration) UnmarshalText(text []byte) error { 623 | // var err error 624 | // d.Duration, err = time.ParseDuration(string(text)) 625 | // return err 626 | // } 627 | 628 | for _, s := range favorites.Song { 629 | fmt.Printf("%s (%s)\n", s.Name, s.Duration) 630 | } 631 | // Output: 632 | // Thunder Road (4m49s) 633 | // Stairway to Heaven (8m3s) 634 | } 635 | 636 | // Example StrictDecoding shows how to detect whether there are keys in the 637 | // TOML document that weren't decoded into the value given. This is useful 638 | // for returning an error to the user if they've included extraneous fields 639 | // in their configuration. 640 | func Example_strictDecoding() { 641 | var blob = ` 642 | key1 = "value1" 643 | key2 = "value2" 644 | key3 = "value3" 645 | ` 646 | type config struct { 647 | Key1 string 648 | Key3 string 649 | } 650 | 651 | var conf config 652 | md, err := Decode(blob, &conf) 653 | if err != nil { 654 | log.Fatal(err) 655 | } 656 | fmt.Printf("Undecoded keys: %q\n", md.Undecoded()) 657 | // Output: 658 | // Undecoded keys: ["key2"] 659 | } 660 | 661 | // Example UnmarshalTOML shows how to implement a struct type that knows how to 662 | // unmarshal itself. The struct must take full responsibility for mapping the 663 | // values passed into the struct. The method may be used with interfaces in a 664 | // struct in cases where the actual type is not known until the data is 665 | // examined. 666 | func Example_unmarshalTOML() { 667 | 668 | var blob = ` 669 | [[parts]] 670 | type = "valve" 671 | id = "valve-1" 672 | size = 1.2 673 | rating = 4 674 | 675 | [[parts]] 676 | type = "valve" 677 | id = "valve-2" 678 | size = 2.1 679 | rating = 5 680 | 681 | [[parts]] 682 | type = "pipe" 683 | id = "pipe-1" 684 | length = 2.1 685 | diameter = 12 686 | 687 | [[parts]] 688 | type = "cable" 689 | id = "cable-1" 690 | length = 12 691 | rating = 3.1 692 | ` 693 | o := &order{} 694 | err := Unmarshal([]byte(blob), o) 695 | if err != nil { 696 | log.Fatal(err) 697 | } 698 | 699 | fmt.Println(len(o.parts)) 700 | 701 | for _, part := range o.parts { 702 | fmt.Println(part.Name()) 703 | } 704 | 705 | // Code to implement UmarshalJSON. 706 | 707 | // type order struct { 708 | // // NOTE `order.parts` is a private slice of type `part` which is an 709 | // // interface and may only be loaded from toml using the 710 | // // UnmarshalTOML() method of the Umarshaler interface. 711 | // parts parts 712 | // } 713 | 714 | // func (o *order) UnmarshalTOML(data interface{}) error { 715 | 716 | // // NOTE the example below contains detailed type casting to show how 717 | // // the 'data' is retrieved. In operational use, a type cast wrapper 718 | // // may be prefered e.g. 719 | // // 720 | // // func AsMap(v interface{}) (map[string]interface{}, error) { 721 | // // return v.(map[string]interface{}) 722 | // // } 723 | // // 724 | // // resulting in: 725 | // // d, _ := AsMap(data) 726 | // // 727 | 728 | // d, _ := data.(map[string]interface{}) 729 | // parts, _ := d["parts"].([]map[string]interface{}) 730 | 731 | // for _, p := range parts { 732 | 733 | // typ, _ := p["type"].(string) 734 | // id, _ := p["id"].(string) 735 | 736 | // // detect the type of part and handle each case 737 | // switch p["type"] { 738 | // case "valve": 739 | 740 | // size := float32(p["size"].(float64)) 741 | // rating := int(p["rating"].(int64)) 742 | 743 | // valve := &valve{ 744 | // Type: typ, 745 | // ID: id, 746 | // Size: size, 747 | // Rating: rating, 748 | // } 749 | 750 | // o.parts = append(o.parts, valve) 751 | 752 | // case "pipe": 753 | 754 | // length := float32(p["length"].(float64)) 755 | // diameter := int(p["diameter"].(int64)) 756 | 757 | // pipe := &pipe{ 758 | // Type: typ, 759 | // ID: id, 760 | // Length: length, 761 | // Diameter: diameter, 762 | // } 763 | 764 | // o.parts = append(o.parts, pipe) 765 | 766 | // case "cable": 767 | 768 | // length := int(p["length"].(int64)) 769 | // rating := float32(p["rating"].(float64)) 770 | 771 | // cable := &cable{ 772 | // Type: typ, 773 | // ID: id, 774 | // Length: length, 775 | // Rating: rating, 776 | // } 777 | 778 | // o.parts = append(o.parts, cable) 779 | 780 | // } 781 | // } 782 | 783 | // return nil 784 | // } 785 | 786 | // type parts []part 787 | 788 | // type part interface { 789 | // Name() string 790 | // } 791 | 792 | // type valve struct { 793 | // Type string 794 | // ID string 795 | // Size float32 796 | // Rating int 797 | // } 798 | 799 | // func (v *valve) Name() string { 800 | // return fmt.Sprintf("VALVE: %s", v.ID) 801 | // } 802 | 803 | // type pipe struct { 804 | // Type string 805 | // ID string 806 | // Length float32 807 | // Diameter int 808 | // } 809 | 810 | // func (p *pipe) Name() string { 811 | // return fmt.Sprintf("PIPE: %s", p.ID) 812 | // } 813 | 814 | // type cable struct { 815 | // Type string 816 | // ID string 817 | // Length int 818 | // Rating float32 819 | // } 820 | 821 | // func (c *cable) Name() string { 822 | // return fmt.Sprintf("CABLE: %s", c.ID) 823 | // } 824 | 825 | // Output: 826 | // 4 827 | // VALVE: valve-1 828 | // VALVE: valve-2 829 | // PIPE: pipe-1 830 | // CABLE: cable-1 831 | 832 | } 833 | 834 | type order struct { 835 | // NOTE `order.parts` is a private slice of type `part` which is an 836 | // interface and may only be loaded from toml using the UnmarshalTOML() 837 | // method of the Umarshaler interface. 838 | parts parts 839 | } 840 | 841 | func (o *order) UnmarshalTOML(data interface{}) error { 842 | 843 | // NOTE the example below contains detailed type casting to show how 844 | // the 'data' is retrieved. In operational use, a type cast wrapper 845 | // may be prefered e.g. 846 | // 847 | // func AsMap(v interface{}) (map[string]interface{}, error) { 848 | // return v.(map[string]interface{}) 849 | // } 850 | // 851 | // resulting in: 852 | // d, _ := AsMap(data) 853 | // 854 | 855 | d, _ := data.(map[string]interface{}) 856 | parts, _ := d["parts"].([]map[string]interface{}) 857 | 858 | for _, p := range parts { 859 | 860 | typ, _ := p["type"].(string) 861 | id, _ := p["id"].(string) 862 | 863 | // detect the type of part and handle each case 864 | switch p["type"] { 865 | case "valve": 866 | 867 | size := float32(p["size"].(float64)) 868 | rating := int(p["rating"].(int64)) 869 | 870 | valve := &valve{ 871 | Type: typ, 872 | ID: id, 873 | Size: size, 874 | Rating: rating, 875 | } 876 | 877 | o.parts = append(o.parts, valve) 878 | 879 | case "pipe": 880 | 881 | length := float32(p["length"].(float64)) 882 | diameter := int(p["diameter"].(int64)) 883 | 884 | pipe := &pipe{ 885 | Type: typ, 886 | ID: id, 887 | Length: length, 888 | Diameter: diameter, 889 | } 890 | 891 | o.parts = append(o.parts, pipe) 892 | 893 | case "cable": 894 | 895 | length := int(p["length"].(int64)) 896 | rating := float32(p["rating"].(float64)) 897 | 898 | cable := &cable{ 899 | Type: typ, 900 | ID: id, 901 | Length: length, 902 | Rating: rating, 903 | } 904 | 905 | o.parts = append(o.parts, cable) 906 | 907 | } 908 | } 909 | 910 | return nil 911 | } 912 | 913 | type parts []part 914 | 915 | type part interface { 916 | Name() string 917 | } 918 | 919 | type valve struct { 920 | Type string 921 | ID string 922 | Size float32 923 | Rating int 924 | } 925 | 926 | func (v *valve) Name() string { 927 | return fmt.Sprintf("VALVE: %s", v.ID) 928 | } 929 | 930 | type pipe struct { 931 | Type string 932 | ID string 933 | Length float32 934 | Diameter int 935 | } 936 | 937 | func (p *pipe) Name() string { 938 | return fmt.Sprintf("PIPE: %s", p.ID) 939 | } 940 | 941 | type cable struct { 942 | Type string 943 | ID string 944 | Length int 945 | Rating float32 946 | } 947 | 948 | func (c *cable) Name() string { 949 | return fmt.Sprintf("CABLE: %s", c.ID) 950 | } 951 | -------------------------------------------------------------------------------- /_third_party/github.com/BurntSushi/toml/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package toml provides facilities for decoding and encoding TOML configuration 3 | files via reflection. There is also support for delaying decoding with 4 | the Primitive type, and querying the set of keys in a TOML document with the 5 | MetaData type. 6 | 7 | The specification implemented: https://github.com/mojombo/toml 8 | 9 | The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify 10 | whether a file is a valid TOML document. It can also be used to print the 11 | type of each key in a TOML document. 12 | 13 | Testing 14 | 15 | There are two important types of tests used for this package. The first is 16 | contained inside '*_test.go' files and uses the standard Go unit testing 17 | framework. These tests are primarily devoted to holistically testing the 18 | decoder and encoder. 19 | 20 | The second type of testing is used to verify the implementation's adherence 21 | to the TOML specification. These tests have been factored into their own 22 | project: https://github.com/BurntSushi/toml-test 23 | 24 | The reason the tests are in a separate project is so that they can be used by 25 | any implementation of TOML. Namely, it is language agnostic. 26 | */ 27 | package toml 28 | -------------------------------------------------------------------------------- /_third_party/github.com/BurntSushi/toml/encode.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "reflect" 9 | "sort" 10 | "strconv" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | type tomlEncodeError struct{ error } 16 | 17 | var ( 18 | errArrayMixedElementTypes = errors.New( 19 | "can't encode array with mixed element types") 20 | errArrayNilElement = errors.New( 21 | "can't encode array with nil element") 22 | errNonString = errors.New( 23 | "can't encode a map with non-string key type") 24 | errAnonNonStruct = errors.New( 25 | "can't encode an anonymous field that is not a struct") 26 | errArrayNoTable = errors.New( 27 | "TOML array element can't contain a table") 28 | errNoKey = errors.New( 29 | "top-level values must be a Go map or struct") 30 | errAnything = errors.New("") // used in testing 31 | ) 32 | 33 | var quotedReplacer = strings.NewReplacer( 34 | "\t", "\\t", 35 | "\n", "\\n", 36 | "\r", "\\r", 37 | "\"", "\\\"", 38 | "\\", "\\\\", 39 | ) 40 | 41 | // Encoder controls the encoding of Go values to a TOML document to some 42 | // io.Writer. 43 | // 44 | // The indentation level can be controlled with the Indent field. 45 | type Encoder struct { 46 | // A single indentation level. By default it is two spaces. 47 | Indent string 48 | 49 | // hasWritten is whether we have written any output to w yet. 50 | hasWritten bool 51 | w *bufio.Writer 52 | } 53 | 54 | // NewEncoder returns a TOML encoder that encodes Go values to the io.Writer 55 | // given. By default, a single indentation level is 2 spaces. 56 | func NewEncoder(w io.Writer) *Encoder { 57 | return &Encoder{ 58 | w: bufio.NewWriter(w), 59 | Indent: " ", 60 | } 61 | } 62 | 63 | // Encode writes a TOML representation of the Go value to the underlying 64 | // io.Writer. If the value given cannot be encoded to a valid TOML document, 65 | // then an error is returned. 66 | // 67 | // The mapping between Go values and TOML values should be precisely the same 68 | // as for the Decode* functions. Similarly, the TextMarshaler interface is 69 | // supported by encoding the resulting bytes as strings. (If you want to write 70 | // arbitrary binary data then you will need to use something like base64 since 71 | // TOML does not have any binary types.) 72 | // 73 | // When encoding TOML hashes (i.e., Go maps or structs), keys without any 74 | // sub-hashes are encoded first. 75 | // 76 | // If a Go map is encoded, then its keys are sorted alphabetically for 77 | // deterministic output. More control over this behavior may be provided if 78 | // there is demand for it. 79 | // 80 | // Encoding Go values without a corresponding TOML representation---like map 81 | // types with non-string keys---will cause an error to be returned. Similarly 82 | // for mixed arrays/slices, arrays/slices with nil elements, embedded 83 | // non-struct types and nested slices containing maps or structs. 84 | // (e.g., [][]map[string]string is not allowed but []map[string]string is OK 85 | // and so is []map[string][]string.) 86 | func (enc *Encoder) Encode(v interface{}) error { 87 | rv := eindirect(reflect.ValueOf(v)) 88 | if err := enc.safeEncode(Key([]string{}), rv); err != nil { 89 | return err 90 | } 91 | return enc.w.Flush() 92 | } 93 | 94 | func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) { 95 | defer func() { 96 | if r := recover(); r != nil { 97 | if terr, ok := r.(tomlEncodeError); ok { 98 | err = terr.error 99 | return 100 | } 101 | panic(r) 102 | } 103 | }() 104 | enc.encode(key, rv) 105 | return nil 106 | } 107 | 108 | func (enc *Encoder) encode(key Key, rv reflect.Value) { 109 | // Special case. Time needs to be in ISO8601 format. 110 | // Special case. If we can marshal the type to text, then we used that. 111 | // Basically, this prevents the encoder for handling these types as 112 | // generic structs (or whatever the underlying type of a TextMarshaler is). 113 | switch rv.Interface().(type) { 114 | case time.Time, TextMarshaler: 115 | enc.keyEqElement(key, rv) 116 | return 117 | } 118 | 119 | k := rv.Kind() 120 | switch k { 121 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 122 | reflect.Int64, 123 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, 124 | reflect.Uint64, 125 | reflect.Float32, reflect.Float64, reflect.String, reflect.Bool: 126 | enc.keyEqElement(key, rv) 127 | case reflect.Array, reflect.Slice: 128 | if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) { 129 | enc.eArrayOfTables(key, rv) 130 | } else { 131 | enc.keyEqElement(key, rv) 132 | } 133 | case reflect.Interface: 134 | if rv.IsNil() { 135 | return 136 | } 137 | enc.encode(key, rv.Elem()) 138 | case reflect.Map: 139 | if rv.IsNil() { 140 | return 141 | } 142 | enc.eTable(key, rv) 143 | case reflect.Ptr: 144 | if rv.IsNil() { 145 | return 146 | } 147 | enc.encode(key, rv.Elem()) 148 | case reflect.Struct: 149 | enc.eTable(key, rv) 150 | default: 151 | panic(e("Unsupported type for key '%s': %s", key, k)) 152 | } 153 | } 154 | 155 | // eElement encodes any value that can be an array element (primitives and 156 | // arrays). 157 | func (enc *Encoder) eElement(rv reflect.Value) { 158 | switch v := rv.Interface().(type) { 159 | case time.Time: 160 | // Special case time.Time as a primitive. Has to come before 161 | // TextMarshaler below because time.Time implements 162 | // encoding.TextMarshaler, but we need to always use UTC. 163 | enc.wf(v.In(time.FixedZone("UTC", 0)).Format("2006-01-02T15:04:05Z")) 164 | return 165 | case TextMarshaler: 166 | // Special case. Use text marshaler if it's available for this value. 167 | if s, err := v.MarshalText(); err != nil { 168 | encPanic(err) 169 | } else { 170 | enc.writeQuoted(string(s)) 171 | } 172 | return 173 | } 174 | switch rv.Kind() { 175 | case reflect.Bool: 176 | enc.wf(strconv.FormatBool(rv.Bool())) 177 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 178 | reflect.Int64: 179 | enc.wf(strconv.FormatInt(rv.Int(), 10)) 180 | case reflect.Uint, reflect.Uint8, reflect.Uint16, 181 | reflect.Uint32, reflect.Uint64: 182 | enc.wf(strconv.FormatUint(rv.Uint(), 10)) 183 | case reflect.Float32: 184 | enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 32))) 185 | case reflect.Float64: 186 | enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 64))) 187 | case reflect.Array, reflect.Slice: 188 | enc.eArrayOrSliceElement(rv) 189 | case reflect.Interface: 190 | enc.eElement(rv.Elem()) 191 | case reflect.String: 192 | enc.writeQuoted(rv.String()) 193 | default: 194 | panic(e("Unexpected primitive type: %s", rv.Kind())) 195 | } 196 | } 197 | 198 | // By the TOML spec, all floats must have a decimal with at least one 199 | // number on either side. 200 | func floatAddDecimal(fstr string) string { 201 | if !strings.Contains(fstr, ".") { 202 | return fstr + ".0" 203 | } 204 | return fstr 205 | } 206 | 207 | func (enc *Encoder) writeQuoted(s string) { 208 | enc.wf("\"%s\"", quotedReplacer.Replace(s)) 209 | } 210 | 211 | func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) { 212 | length := rv.Len() 213 | enc.wf("[") 214 | for i := 0; i < length; i++ { 215 | elem := rv.Index(i) 216 | enc.eElement(elem) 217 | if i != length-1 { 218 | enc.wf(", ") 219 | } 220 | } 221 | enc.wf("]") 222 | } 223 | 224 | func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) { 225 | if len(key) == 0 { 226 | encPanic(errNoKey) 227 | } 228 | for i := 0; i < rv.Len(); i++ { 229 | trv := rv.Index(i) 230 | if isNil(trv) { 231 | continue 232 | } 233 | panicIfInvalidKey(key) 234 | enc.newline() 235 | enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll()) 236 | enc.newline() 237 | enc.eMapOrStruct(key, trv) 238 | } 239 | } 240 | 241 | func (enc *Encoder) eTable(key Key, rv reflect.Value) { 242 | panicIfInvalidKey(key) 243 | if len(key) == 1 { 244 | // Output an extra new line between top-level tables. 245 | // (The newline isn't written if nothing else has been written though.) 246 | enc.newline() 247 | } 248 | if len(key) > 0 { 249 | enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll()) 250 | enc.newline() 251 | } 252 | enc.eMapOrStruct(key, rv) 253 | } 254 | 255 | func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) { 256 | switch rv := eindirect(rv); rv.Kind() { 257 | case reflect.Map: 258 | enc.eMap(key, rv) 259 | case reflect.Struct: 260 | enc.eStruct(key, rv) 261 | default: 262 | panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String()) 263 | } 264 | } 265 | 266 | func (enc *Encoder) eMap(key Key, rv reflect.Value) { 267 | rt := rv.Type() 268 | if rt.Key().Kind() != reflect.String { 269 | encPanic(errNonString) 270 | } 271 | 272 | // Sort keys so that we have deterministic output. And write keys directly 273 | // underneath this key first, before writing sub-structs or sub-maps. 274 | var mapKeysDirect, mapKeysSub []string 275 | for _, mapKey := range rv.MapKeys() { 276 | k := mapKey.String() 277 | if typeIsHash(tomlTypeOfGo(rv.MapIndex(mapKey))) { 278 | mapKeysSub = append(mapKeysSub, k) 279 | } else { 280 | mapKeysDirect = append(mapKeysDirect, k) 281 | } 282 | } 283 | 284 | var writeMapKeys = func(mapKeys []string) { 285 | sort.Strings(mapKeys) 286 | for _, mapKey := range mapKeys { 287 | mrv := rv.MapIndex(reflect.ValueOf(mapKey)) 288 | if isNil(mrv) { 289 | // Don't write anything for nil fields. 290 | continue 291 | } 292 | enc.encode(key.add(mapKey), mrv) 293 | } 294 | } 295 | writeMapKeys(mapKeysDirect) 296 | writeMapKeys(mapKeysSub) 297 | } 298 | 299 | func (enc *Encoder) eStruct(key Key, rv reflect.Value) { 300 | // Write keys for fields directly under this key first, because if we write 301 | // a field that creates a new table, then all keys under it will be in that 302 | // table (not the one we're writing here). 303 | rt := rv.Type() 304 | var fieldsDirect, fieldsSub [][]int 305 | var addFields func(rt reflect.Type, rv reflect.Value, start []int) 306 | addFields = func(rt reflect.Type, rv reflect.Value, start []int) { 307 | for i := 0; i < rt.NumField(); i++ { 308 | f := rt.Field(i) 309 | // skip unexporded fields 310 | if f.PkgPath != "" { 311 | continue 312 | } 313 | frv := rv.Field(i) 314 | if f.Anonymous { 315 | frv := eindirect(frv) 316 | t := frv.Type() 317 | if t.Kind() != reflect.Struct { 318 | encPanic(errAnonNonStruct) 319 | } 320 | addFields(t, frv, f.Index) 321 | } else if typeIsHash(tomlTypeOfGo(frv)) { 322 | fieldsSub = append(fieldsSub, append(start, f.Index...)) 323 | } else { 324 | fieldsDirect = append(fieldsDirect, append(start, f.Index...)) 325 | } 326 | } 327 | } 328 | addFields(rt, rv, nil) 329 | 330 | var writeFields = func(fields [][]int) { 331 | for _, fieldIndex := range fields { 332 | sft := rt.FieldByIndex(fieldIndex) 333 | sf := rv.FieldByIndex(fieldIndex) 334 | if isNil(sf) { 335 | // Don't write anything for nil fields. 336 | continue 337 | } 338 | 339 | keyName := sft.Tag.Get("toml") 340 | if keyName == "-" { 341 | continue 342 | } 343 | if keyName == "" { 344 | keyName = sft.Name 345 | } 346 | 347 | keyName, opts := getOptions(keyName) 348 | if _, ok := opts["omitempty"]; ok && isEmpty(sf) { 349 | continue 350 | } else if _, ok := opts["omitzero"]; ok && isZero(sf) { 351 | continue 352 | } 353 | 354 | enc.encode(key.add(keyName), sf) 355 | } 356 | } 357 | writeFields(fieldsDirect) 358 | writeFields(fieldsSub) 359 | } 360 | 361 | // tomlTypeName returns the TOML type name of the Go value's type. It is 362 | // used to determine whether the types of array elements are mixed (which is 363 | // forbidden). If the Go value is nil, then it is illegal for it to be an array 364 | // element, and valueIsNil is returned as true. 365 | 366 | // Returns the TOML type of a Go value. The type may be `nil`, which means 367 | // no concrete TOML type could be found. 368 | func tomlTypeOfGo(rv reflect.Value) tomlType { 369 | if isNil(rv) || !rv.IsValid() { 370 | return nil 371 | } 372 | switch rv.Kind() { 373 | case reflect.Bool: 374 | return tomlBool 375 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 376 | reflect.Int64, 377 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, 378 | reflect.Uint64: 379 | return tomlInteger 380 | case reflect.Float32, reflect.Float64: 381 | return tomlFloat 382 | case reflect.Array, reflect.Slice: 383 | if typeEqual(tomlHash, tomlArrayType(rv)) { 384 | return tomlArrayHash 385 | } else { 386 | return tomlArray 387 | } 388 | case reflect.Ptr, reflect.Interface: 389 | return tomlTypeOfGo(rv.Elem()) 390 | case reflect.String: 391 | return tomlString 392 | case reflect.Map: 393 | return tomlHash 394 | case reflect.Struct: 395 | switch rv.Interface().(type) { 396 | case time.Time: 397 | return tomlDatetime 398 | case TextMarshaler: 399 | return tomlString 400 | default: 401 | return tomlHash 402 | } 403 | default: 404 | panic("unexpected reflect.Kind: " + rv.Kind().String()) 405 | } 406 | } 407 | 408 | // tomlArrayType returns the element type of a TOML array. The type returned 409 | // may be nil if it cannot be determined (e.g., a nil slice or a zero length 410 | // slize). This function may also panic if it finds a type that cannot be 411 | // expressed in TOML (such as nil elements, heterogeneous arrays or directly 412 | // nested arrays of tables). 413 | func tomlArrayType(rv reflect.Value) tomlType { 414 | if isNil(rv) || !rv.IsValid() || rv.Len() == 0 { 415 | return nil 416 | } 417 | firstType := tomlTypeOfGo(rv.Index(0)) 418 | if firstType == nil { 419 | encPanic(errArrayNilElement) 420 | } 421 | 422 | rvlen := rv.Len() 423 | for i := 1; i < rvlen; i++ { 424 | elem := rv.Index(i) 425 | switch elemType := tomlTypeOfGo(elem); { 426 | case elemType == nil: 427 | encPanic(errArrayNilElement) 428 | case !typeEqual(firstType, elemType): 429 | encPanic(errArrayMixedElementTypes) 430 | } 431 | } 432 | // If we have a nested array, then we must make sure that the nested 433 | // array contains ONLY primitives. 434 | // This checks arbitrarily nested arrays. 435 | if typeEqual(firstType, tomlArray) || typeEqual(firstType, tomlArrayHash) { 436 | nest := tomlArrayType(eindirect(rv.Index(0))) 437 | if typeEqual(nest, tomlHash) || typeEqual(nest, tomlArrayHash) { 438 | encPanic(errArrayNoTable) 439 | } 440 | } 441 | return firstType 442 | } 443 | 444 | func getOptions(keyName string) (string, map[string]struct{}) { 445 | opts := make(map[string]struct{}) 446 | ss := strings.Split(keyName, ",") 447 | name := ss[0] 448 | if len(ss) > 1 { 449 | for _, opt := range ss { 450 | opts[opt] = struct{}{} 451 | } 452 | } 453 | 454 | return name, opts 455 | } 456 | 457 | func isZero(rv reflect.Value) bool { 458 | switch rv.Kind() { 459 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 460 | if rv.Int() == 0 { 461 | return true 462 | } 463 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 464 | if rv.Uint() == 0 { 465 | return true 466 | } 467 | case reflect.Float32, reflect.Float64: 468 | if rv.Float() == 0.0 { 469 | return true 470 | } 471 | } 472 | 473 | return false 474 | } 475 | 476 | func isEmpty(rv reflect.Value) bool { 477 | switch rv.Kind() { 478 | case reflect.String: 479 | if len(strings.TrimSpace(rv.String())) == 0 { 480 | return true 481 | } 482 | case reflect.Array, reflect.Slice, reflect.Map: 483 | if rv.Len() == 0 { 484 | return true 485 | } 486 | } 487 | 488 | return false 489 | } 490 | 491 | func (enc *Encoder) newline() { 492 | if enc.hasWritten { 493 | enc.wf("\n") 494 | } 495 | } 496 | 497 | func (enc *Encoder) keyEqElement(key Key, val reflect.Value) { 498 | if len(key) == 0 { 499 | encPanic(errNoKey) 500 | } 501 | panicIfInvalidKey(key) 502 | enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1)) 503 | enc.eElement(val) 504 | enc.newline() 505 | } 506 | 507 | func (enc *Encoder) wf(format string, v ...interface{}) { 508 | if _, err := fmt.Fprintf(enc.w, format, v...); err != nil { 509 | encPanic(err) 510 | } 511 | enc.hasWritten = true 512 | } 513 | 514 | func (enc *Encoder) indentStr(key Key) string { 515 | return strings.Repeat(enc.Indent, len(key)-1) 516 | } 517 | 518 | func encPanic(err error) { 519 | panic(tomlEncodeError{err}) 520 | } 521 | 522 | func eindirect(v reflect.Value) reflect.Value { 523 | switch v.Kind() { 524 | case reflect.Ptr, reflect.Interface: 525 | return eindirect(v.Elem()) 526 | default: 527 | return v 528 | } 529 | } 530 | 531 | func isNil(rv reflect.Value) bool { 532 | switch rv.Kind() { 533 | case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: 534 | return rv.IsNil() 535 | default: 536 | return false 537 | } 538 | } 539 | 540 | func panicIfInvalidKey(key Key) { 541 | for _, k := range key { 542 | if len(k) == 0 { 543 | encPanic(e("Key '%s' is not a valid table name. Key names "+ 544 | "cannot be empty.", key.maybeQuotedAll())) 545 | } 546 | } 547 | } 548 | 549 | func isValidKeyName(s string) bool { 550 | return len(s) != 0 551 | } 552 | -------------------------------------------------------------------------------- /_third_party/github.com/BurntSushi/toml/encode_test.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "net" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestEncodeRoundTrip(t *testing.T) { 13 | type Config struct { 14 | Age int 15 | Cats []string 16 | Pi float64 17 | Perfection []int 18 | DOB time.Time 19 | Ipaddress net.IP 20 | } 21 | 22 | var inputs = Config{ 23 | 13, 24 | []string{"one", "two", "three"}, 25 | 3.145, 26 | []int{11, 2, 3, 4}, 27 | time.Now(), 28 | net.ParseIP("192.168.59.254"), 29 | } 30 | 31 | var firstBuffer bytes.Buffer 32 | e := NewEncoder(&firstBuffer) 33 | err := e.Encode(inputs) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | var outputs Config 38 | if _, err := Decode(firstBuffer.String(), &outputs); err != nil { 39 | log.Printf("Could not decode:\n-----\n%s\n-----\n", 40 | firstBuffer.String()) 41 | t.Fatal(err) 42 | } 43 | 44 | // could test each value individually, but I'm lazy 45 | var secondBuffer bytes.Buffer 46 | e2 := NewEncoder(&secondBuffer) 47 | err = e2.Encode(outputs) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | if firstBuffer.String() != secondBuffer.String() { 52 | t.Error( 53 | firstBuffer.String(), 54 | "\n\n is not identical to\n\n", 55 | secondBuffer.String()) 56 | } 57 | } 58 | 59 | // XXX(burntsushi) 60 | // I think these tests probably should be removed. They are good, but they 61 | // ought to be obsolete by toml-test. 62 | func TestEncode(t *testing.T) { 63 | type Embedded struct { 64 | Int int `toml:"_int"` 65 | } 66 | type NonStruct int 67 | 68 | date := time.Date(2014, 5, 11, 20, 30, 40, 0, time.FixedZone("IST", 3600)) 69 | dateStr := "2014-05-11T19:30:40Z" 70 | 71 | tests := map[string]struct { 72 | input interface{} 73 | wantOutput string 74 | wantError error 75 | }{ 76 | "bool field": { 77 | input: struct { 78 | BoolTrue bool 79 | BoolFalse bool 80 | }{true, false}, 81 | wantOutput: "BoolTrue = true\nBoolFalse = false\n", 82 | }, 83 | "int fields": { 84 | input: struct { 85 | Int int 86 | Int8 int8 87 | Int16 int16 88 | Int32 int32 89 | Int64 int64 90 | }{1, 2, 3, 4, 5}, 91 | wantOutput: "Int = 1\nInt8 = 2\nInt16 = 3\nInt32 = 4\nInt64 = 5\n", 92 | }, 93 | "uint fields": { 94 | input: struct { 95 | Uint uint 96 | Uint8 uint8 97 | Uint16 uint16 98 | Uint32 uint32 99 | Uint64 uint64 100 | }{1, 2, 3, 4, 5}, 101 | wantOutput: "Uint = 1\nUint8 = 2\nUint16 = 3\nUint32 = 4" + 102 | "\nUint64 = 5\n", 103 | }, 104 | "float fields": { 105 | input: struct { 106 | Float32 float32 107 | Float64 float64 108 | }{1.5, 2.5}, 109 | wantOutput: "Float32 = 1.5\nFloat64 = 2.5\n", 110 | }, 111 | "string field": { 112 | input: struct{ String string }{"foo"}, 113 | wantOutput: "String = \"foo\"\n", 114 | }, 115 | "string field and unexported field": { 116 | input: struct { 117 | String string 118 | unexported int 119 | }{"foo", 0}, 120 | wantOutput: "String = \"foo\"\n", 121 | }, 122 | "datetime field in UTC": { 123 | input: struct{ Date time.Time }{date}, 124 | wantOutput: fmt.Sprintf("Date = %s\n", dateStr), 125 | }, 126 | "datetime field as primitive": { 127 | // Using a map here to fail if isStructOrMap() returns true for 128 | // time.Time. 129 | input: map[string]interface{}{ 130 | "Date": date, 131 | "Int": 1, 132 | }, 133 | wantOutput: fmt.Sprintf("Date = %s\nInt = 1\n", dateStr), 134 | }, 135 | "array fields": { 136 | input: struct { 137 | IntArray0 [0]int 138 | IntArray3 [3]int 139 | }{[0]int{}, [3]int{1, 2, 3}}, 140 | wantOutput: "IntArray0 = []\nIntArray3 = [1, 2, 3]\n", 141 | }, 142 | "slice fields": { 143 | input: struct{ IntSliceNil, IntSlice0, IntSlice3 []int }{ 144 | nil, []int{}, []int{1, 2, 3}, 145 | }, 146 | wantOutput: "IntSlice0 = []\nIntSlice3 = [1, 2, 3]\n", 147 | }, 148 | "datetime slices": { 149 | input: struct{ DatetimeSlice []time.Time }{ 150 | []time.Time{date, date}, 151 | }, 152 | wantOutput: fmt.Sprintf("DatetimeSlice = [%s, %s]\n", 153 | dateStr, dateStr), 154 | }, 155 | "nested arrays and slices": { 156 | input: struct { 157 | SliceOfArrays [][2]int 158 | ArrayOfSlices [2][]int 159 | SliceOfArraysOfSlices [][2][]int 160 | ArrayOfSlicesOfArrays [2][][2]int 161 | SliceOfMixedArrays [][2]interface{} 162 | ArrayOfMixedSlices [2][]interface{} 163 | }{ 164 | [][2]int{{1, 2}, {3, 4}}, 165 | [2][]int{{1, 2}, {3, 4}}, 166 | [][2][]int{ 167 | { 168 | {1, 2}, {3, 4}, 169 | }, 170 | { 171 | {5, 6}, {7, 8}, 172 | }, 173 | }, 174 | [2][][2]int{ 175 | { 176 | {1, 2}, {3, 4}, 177 | }, 178 | { 179 | {5, 6}, {7, 8}, 180 | }, 181 | }, 182 | [][2]interface{}{ 183 | {1, 2}, {"a", "b"}, 184 | }, 185 | [2][]interface{}{ 186 | {1, 2}, {"a", "b"}, 187 | }, 188 | }, 189 | wantOutput: `SliceOfArrays = [[1, 2], [3, 4]] 190 | ArrayOfSlices = [[1, 2], [3, 4]] 191 | SliceOfArraysOfSlices = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] 192 | ArrayOfSlicesOfArrays = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]] 193 | SliceOfMixedArrays = [[1, 2], ["a", "b"]] 194 | ArrayOfMixedSlices = [[1, 2], ["a", "b"]] 195 | `, 196 | }, 197 | "empty slice": { 198 | input: struct{ Empty []interface{} }{[]interface{}{}}, 199 | wantOutput: "Empty = []\n", 200 | }, 201 | "(error) slice with element type mismatch (string and integer)": { 202 | input: struct{ Mixed []interface{} }{[]interface{}{1, "a"}}, 203 | wantError: errArrayMixedElementTypes, 204 | }, 205 | "(error) slice with element type mismatch (integer and float)": { 206 | input: struct{ Mixed []interface{} }{[]interface{}{1, 2.5}}, 207 | wantError: errArrayMixedElementTypes, 208 | }, 209 | "slice with elems of differing Go types, same TOML types": { 210 | input: struct { 211 | MixedInts []interface{} 212 | MixedFloats []interface{} 213 | }{ 214 | []interface{}{ 215 | int(1), int8(2), int16(3), int32(4), int64(5), 216 | uint(1), uint8(2), uint16(3), uint32(4), uint64(5), 217 | }, 218 | []interface{}{float32(1.5), float64(2.5)}, 219 | }, 220 | wantOutput: "MixedInts = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]\n" + 221 | "MixedFloats = [1.5, 2.5]\n", 222 | }, 223 | "(error) slice w/ element type mismatch (one is nested array)": { 224 | input: struct{ Mixed []interface{} }{ 225 | []interface{}{1, []interface{}{2}}, 226 | }, 227 | wantError: errArrayMixedElementTypes, 228 | }, 229 | "(error) slice with 1 nil element": { 230 | input: struct{ NilElement1 []interface{} }{[]interface{}{nil}}, 231 | wantError: errArrayNilElement, 232 | }, 233 | "(error) slice with 1 nil element (and other non-nil elements)": { 234 | input: struct{ NilElement []interface{} }{ 235 | []interface{}{1, nil}, 236 | }, 237 | wantError: errArrayNilElement, 238 | }, 239 | "simple map": { 240 | input: map[string]int{"a": 1, "b": 2}, 241 | wantOutput: "a = 1\nb = 2\n", 242 | }, 243 | "map with interface{} value type": { 244 | input: map[string]interface{}{"a": 1, "b": "c"}, 245 | wantOutput: "a = 1\nb = \"c\"\n", 246 | }, 247 | "map with interface{} value type, some of which are structs": { 248 | input: map[string]interface{}{ 249 | "a": struct{ Int int }{2}, 250 | "b": 1, 251 | }, 252 | wantOutput: "b = 1\n\n[a]\n Int = 2\n", 253 | }, 254 | "nested map": { 255 | input: map[string]map[string]int{ 256 | "a": {"b": 1}, 257 | "c": {"d": 2}, 258 | }, 259 | wantOutput: "[a]\n b = 1\n\n[c]\n d = 2\n", 260 | }, 261 | "nested struct": { 262 | input: struct{ Struct struct{ Int int } }{ 263 | struct{ Int int }{1}, 264 | }, 265 | wantOutput: "[Struct]\n Int = 1\n", 266 | }, 267 | "nested struct and non-struct field": { 268 | input: struct { 269 | Struct struct{ Int int } 270 | Bool bool 271 | }{struct{ Int int }{1}, true}, 272 | wantOutput: "Bool = true\n\n[Struct]\n Int = 1\n", 273 | }, 274 | "2 nested structs": { 275 | input: struct{ Struct1, Struct2 struct{ Int int } }{ 276 | struct{ Int int }{1}, struct{ Int int }{2}, 277 | }, 278 | wantOutput: "[Struct1]\n Int = 1\n\n[Struct2]\n Int = 2\n", 279 | }, 280 | "deeply nested structs": { 281 | input: struct { 282 | Struct1, Struct2 struct{ Struct3 *struct{ Int int } } 283 | }{ 284 | struct{ Struct3 *struct{ Int int } }{&struct{ Int int }{1}}, 285 | struct{ Struct3 *struct{ Int int } }{nil}, 286 | }, 287 | wantOutput: "[Struct1]\n [Struct1.Struct3]\n Int = 1" + 288 | "\n\n[Struct2]\n", 289 | }, 290 | "nested struct with nil struct elem": { 291 | input: struct { 292 | Struct struct{ Inner *struct{ Int int } } 293 | }{ 294 | struct{ Inner *struct{ Int int } }{nil}, 295 | }, 296 | wantOutput: "[Struct]\n", 297 | }, 298 | "nested struct with no fields": { 299 | input: struct { 300 | Struct struct{ Inner struct{} } 301 | }{ 302 | struct{ Inner struct{} }{struct{}{}}, 303 | }, 304 | wantOutput: "[Struct]\n [Struct.Inner]\n", 305 | }, 306 | "struct with tags": { 307 | input: struct { 308 | Struct struct { 309 | Int int `toml:"_int"` 310 | } `toml:"_struct"` 311 | Bool bool `toml:"_bool"` 312 | }{ 313 | struct { 314 | Int int `toml:"_int"` 315 | }{1}, true, 316 | }, 317 | wantOutput: "_bool = true\n\n[_struct]\n _int = 1\n", 318 | }, 319 | "embedded struct": { 320 | input: struct{ Embedded }{Embedded{1}}, 321 | wantOutput: "_int = 1\n", 322 | }, 323 | "embedded *struct": { 324 | input: struct{ *Embedded }{&Embedded{1}}, 325 | wantOutput: "_int = 1\n", 326 | }, 327 | "nested embedded struct": { 328 | input: struct { 329 | Struct struct{ Embedded } `toml:"_struct"` 330 | }{struct{ Embedded }{Embedded{1}}}, 331 | wantOutput: "[_struct]\n _int = 1\n", 332 | }, 333 | "nested embedded *struct": { 334 | input: struct { 335 | Struct struct{ *Embedded } `toml:"_struct"` 336 | }{struct{ *Embedded }{&Embedded{1}}}, 337 | wantOutput: "[_struct]\n _int = 1\n", 338 | }, 339 | "array of tables": { 340 | input: struct { 341 | Structs []*struct{ Int int } `toml:"struct"` 342 | }{ 343 | []*struct{ Int int }{{1}, {3}}, 344 | }, 345 | wantOutput: "[[struct]]\n Int = 1\n\n[[struct]]\n Int = 3\n", 346 | }, 347 | "array of tables order": { 348 | input: map[string]interface{}{ 349 | "map": map[string]interface{}{ 350 | "zero": 5, 351 | "arr": []map[string]int{ 352 | { 353 | "friend": 5, 354 | }, 355 | }, 356 | }, 357 | }, 358 | wantOutput: "[map]\n zero = 5\n\n [[map.arr]]\n friend = 5\n", 359 | }, 360 | "(error) top-level slice": { 361 | input: []struct{ Int int }{{1}, {2}, {3}}, 362 | wantError: errNoKey, 363 | }, 364 | "(error) slice of slice": { 365 | input: struct { 366 | Slices [][]struct{ Int int } 367 | }{ 368 | [][]struct{ Int int }{{{1}}, {{2}}, {{3}}}, 369 | }, 370 | wantError: errArrayNoTable, 371 | }, 372 | "(error) map no string key": { 373 | input: map[int]string{1: ""}, 374 | wantError: errNonString, 375 | }, 376 | "(error) anonymous non-struct": { 377 | input: struct{ NonStruct }{5}, 378 | wantError: errAnonNonStruct, 379 | }, 380 | "(error) empty key name": { 381 | input: map[string]int{"": 1}, 382 | wantError: errAnything, 383 | }, 384 | "(error) empty map name": { 385 | input: map[string]interface{}{ 386 | "": map[string]int{"v": 1}, 387 | }, 388 | wantError: errAnything, 389 | }, 390 | } 391 | for label, test := range tests { 392 | encodeExpected(t, label, test.input, test.wantOutput, test.wantError) 393 | } 394 | } 395 | 396 | func TestEncodeNestedTableArrays(t *testing.T) { 397 | type song struct { 398 | Name string `toml:"name"` 399 | } 400 | type album struct { 401 | Name string `toml:"name"` 402 | Songs []song `toml:"songs"` 403 | } 404 | type springsteen struct { 405 | Albums []album `toml:"albums"` 406 | } 407 | value := springsteen{ 408 | []album{ 409 | {"Born to Run", 410 | []song{{"Jungleland"}, {"Meeting Across the River"}}}, 411 | {"Born in the USA", 412 | []song{{"Glory Days"}, {"Dancing in the Dark"}}}, 413 | }, 414 | } 415 | expected := `[[albums]] 416 | name = "Born to Run" 417 | 418 | [[albums.songs]] 419 | name = "Jungleland" 420 | 421 | [[albums.songs]] 422 | name = "Meeting Across the River" 423 | 424 | [[albums]] 425 | name = "Born in the USA" 426 | 427 | [[albums.songs]] 428 | name = "Glory Days" 429 | 430 | [[albums.songs]] 431 | name = "Dancing in the Dark" 432 | ` 433 | encodeExpected(t, "nested table arrays", value, expected, nil) 434 | } 435 | 436 | func TestEncodeArrayHashWithNormalHashOrder(t *testing.T) { 437 | type Alpha struct { 438 | V int 439 | } 440 | type Beta struct { 441 | V int 442 | } 443 | type Conf struct { 444 | V int 445 | A Alpha 446 | B []Beta 447 | } 448 | 449 | val := Conf{ 450 | V: 1, 451 | A: Alpha{2}, 452 | B: []Beta{{3}}, 453 | } 454 | expected := "V = 1\n\n[A]\n V = 2\n\n[[B]]\n V = 3\n" 455 | encodeExpected(t, "array hash with normal hash order", val, expected, nil) 456 | } 457 | 458 | func TestEncodeWithOmitEmpty(t *testing.T) { 459 | type simple struct { 460 | User string `toml:"user"` 461 | Pass string `toml:"password,omitempty"` 462 | } 463 | 464 | value := simple{"Testing", ""} 465 | expected := fmt.Sprintf("user = %q\n", value.User) 466 | encodeExpected(t, "simple with omitempty, is empty", value, expected, nil) 467 | value.Pass = "some password" 468 | expected = fmt.Sprintf("user = %q\npassword = %q\n", value.User, value.Pass) 469 | encodeExpected(t, "simple with omitempty, not empty", value, expected, nil) 470 | } 471 | 472 | func TestEncodeWithOmitZero(t *testing.T) { 473 | type simple struct { 474 | Number int `toml:"number,omitzero"` 475 | Real float64 `toml:"real,omitzero"` 476 | Unsigned uint `toml:"unsigned,omitzero"` 477 | } 478 | 479 | value := simple{0, 0.0, uint(0)} 480 | expected := "" 481 | 482 | encodeExpected(t, "simple with omitzero, all zero", value, expected, nil) 483 | 484 | value.Number = 10 485 | value.Real = 20 486 | value.Unsigned = 5 487 | expected = `number = 10 488 | real = 20.0 489 | unsigned = 5 490 | ` 491 | encodeExpected(t, "simple with omitzero, non-zero", value, expected, nil) 492 | } 493 | 494 | func encodeExpected( 495 | t *testing.T, label string, val interface{}, wantStr string, wantErr error, 496 | ) { 497 | var buf bytes.Buffer 498 | enc := NewEncoder(&buf) 499 | err := enc.Encode(val) 500 | if err != wantErr { 501 | if wantErr != nil { 502 | if wantErr == errAnything && err != nil { 503 | return 504 | } 505 | t.Errorf("%s: want Encode error %v, got %v", label, wantErr, err) 506 | } else { 507 | t.Errorf("%s: Encode failed: %s", label, err) 508 | } 509 | } 510 | if err != nil { 511 | return 512 | } 513 | if got := buf.String(); wantStr != got { 514 | t.Errorf("%s: want\n-----\n%q\n-----\nbut got\n-----\n%q\n-----\n", 515 | label, wantStr, got) 516 | } 517 | } 518 | 519 | func ExampleEncoder_Encode() { 520 | date, _ := time.Parse(time.RFC822, "14 Mar 10 18:00 UTC") 521 | var config = map[string]interface{}{ 522 | "date": date, 523 | "counts": []int{1, 1, 2, 3, 5, 8}, 524 | "hash": map[string]string{ 525 | "key1": "val1", 526 | "key2": "val2", 527 | }, 528 | } 529 | buf := new(bytes.Buffer) 530 | if err := NewEncoder(buf).Encode(config); err != nil { 531 | log.Fatal(err) 532 | } 533 | fmt.Println(buf.String()) 534 | 535 | // Output: 536 | // counts = [1, 1, 2, 3, 5, 8] 537 | // date = 2010-03-14T18:00:00Z 538 | // 539 | // [hash] 540 | // key1 = "val1" 541 | // key2 = "val2" 542 | } 543 | -------------------------------------------------------------------------------- /_third_party/github.com/BurntSushi/toml/encoding_types.go: -------------------------------------------------------------------------------- 1 | // +build go1.2 2 | 3 | package toml 4 | 5 | // In order to support Go 1.1, we define our own TextMarshaler and 6 | // TextUnmarshaler types. For Go 1.2+, we just alias them with the 7 | // standard library interfaces. 8 | 9 | import ( 10 | "encoding" 11 | ) 12 | 13 | // TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here 14 | // so that Go 1.1 can be supported. 15 | type TextMarshaler encoding.TextMarshaler 16 | 17 | // TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined 18 | // here so that Go 1.1 can be supported. 19 | type TextUnmarshaler encoding.TextUnmarshaler 20 | -------------------------------------------------------------------------------- /_third_party/github.com/BurntSushi/toml/encoding_types_1.1.go: -------------------------------------------------------------------------------- 1 | // +build !go1.2 2 | 3 | package toml 4 | 5 | // These interfaces were introduced in Go 1.2, so we add them manually when 6 | // compiling for Go 1.1. 7 | 8 | // TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here 9 | // so that Go 1.1 can be supported. 10 | type TextMarshaler interface { 11 | MarshalText() (text []byte, err error) 12 | } 13 | 14 | // TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined 15 | // here so that Go 1.1 can be supported. 16 | type TextUnmarshaler interface { 17 | UnmarshalText(text []byte) error 18 | } 19 | -------------------------------------------------------------------------------- /_third_party/github.com/BurntSushi/toml/lex.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "unicode/utf8" 7 | ) 8 | 9 | type itemType int 10 | 11 | const ( 12 | itemError itemType = iota 13 | itemNIL // used in the parser to indicate no type 14 | itemEOF 15 | itemText 16 | itemString 17 | itemRawString 18 | itemMultilineString 19 | itemRawMultilineString 20 | itemBool 21 | itemInteger 22 | itemFloat 23 | itemDatetime 24 | itemArray // the start of an array 25 | itemArrayEnd 26 | itemTableStart 27 | itemTableEnd 28 | itemArrayTableStart 29 | itemArrayTableEnd 30 | itemKeyStart 31 | itemCommentStart 32 | ) 33 | 34 | const ( 35 | eof = 0 36 | tableStart = '[' 37 | tableEnd = ']' 38 | arrayTableStart = '[' 39 | arrayTableEnd = ']' 40 | tableSep = '.' 41 | keySep = '=' 42 | arrayStart = '[' 43 | arrayEnd = ']' 44 | arrayValTerm = ',' 45 | commentStart = '#' 46 | stringStart = '"' 47 | stringEnd = '"' 48 | rawStringStart = '\'' 49 | rawStringEnd = '\'' 50 | ) 51 | 52 | type stateFn func(lx *lexer) stateFn 53 | 54 | type lexer struct { 55 | input string 56 | start int 57 | pos int 58 | width int 59 | line int 60 | state stateFn 61 | items chan item 62 | 63 | // A stack of state functions used to maintain context. 64 | // The idea is to reuse parts of the state machine in various places. 65 | // For example, values can appear at the top level or within arbitrarily 66 | // nested arrays. The last state on the stack is used after a value has 67 | // been lexed. Similarly for comments. 68 | stack []stateFn 69 | } 70 | 71 | type item struct { 72 | typ itemType 73 | val string 74 | line int 75 | } 76 | 77 | func (lx *lexer) nextItem() item { 78 | for { 79 | select { 80 | case item := <-lx.items: 81 | return item 82 | default: 83 | lx.state = lx.state(lx) 84 | } 85 | } 86 | } 87 | 88 | func lex(input string) *lexer { 89 | lx := &lexer{ 90 | input: input + "\n", 91 | state: lexTop, 92 | line: 1, 93 | items: make(chan item, 10), 94 | stack: make([]stateFn, 0, 10), 95 | } 96 | return lx 97 | } 98 | 99 | func (lx *lexer) push(state stateFn) { 100 | lx.stack = append(lx.stack, state) 101 | } 102 | 103 | func (lx *lexer) pop() stateFn { 104 | if len(lx.stack) == 0 { 105 | return lx.errorf("BUG in lexer: no states to pop.") 106 | } 107 | last := lx.stack[len(lx.stack)-1] 108 | lx.stack = lx.stack[0 : len(lx.stack)-1] 109 | return last 110 | } 111 | 112 | func (lx *lexer) current() string { 113 | return lx.input[lx.start:lx.pos] 114 | } 115 | 116 | func (lx *lexer) emit(typ itemType) { 117 | lx.items <- item{typ, lx.current(), lx.line} 118 | lx.start = lx.pos 119 | } 120 | 121 | func (lx *lexer) emitTrim(typ itemType) { 122 | lx.items <- item{typ, strings.TrimSpace(lx.current()), lx.line} 123 | lx.start = lx.pos 124 | } 125 | 126 | func (lx *lexer) next() (r rune) { 127 | if lx.pos >= len(lx.input) { 128 | lx.width = 0 129 | return eof 130 | } 131 | 132 | if lx.input[lx.pos] == '\n' { 133 | lx.line++ 134 | } 135 | r, lx.width = utf8.DecodeRuneInString(lx.input[lx.pos:]) 136 | lx.pos += lx.width 137 | return r 138 | } 139 | 140 | // ignore skips over the pending input before this point. 141 | func (lx *lexer) ignore() { 142 | lx.start = lx.pos 143 | } 144 | 145 | // backup steps back one rune. Can be called only once per call of next. 146 | func (lx *lexer) backup() { 147 | lx.pos -= lx.width 148 | if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' { 149 | lx.line-- 150 | } 151 | } 152 | 153 | // accept consumes the next rune if it's equal to `valid`. 154 | func (lx *lexer) accept(valid rune) bool { 155 | if lx.next() == valid { 156 | return true 157 | } 158 | lx.backup() 159 | return false 160 | } 161 | 162 | // peek returns but does not consume the next rune in the input. 163 | func (lx *lexer) peek() rune { 164 | r := lx.next() 165 | lx.backup() 166 | return r 167 | } 168 | 169 | // errorf stops all lexing by emitting an error and returning `nil`. 170 | // Note that any value that is a character is escaped if it's a special 171 | // character (new lines, tabs, etc.). 172 | func (lx *lexer) errorf(format string, values ...interface{}) stateFn { 173 | lx.items <- item{ 174 | itemError, 175 | fmt.Sprintf(format, values...), 176 | lx.line, 177 | } 178 | return nil 179 | } 180 | 181 | // lexTop consumes elements at the top level of TOML data. 182 | func lexTop(lx *lexer) stateFn { 183 | r := lx.next() 184 | if isWhitespace(r) || isNL(r) { 185 | return lexSkip(lx, lexTop) 186 | } 187 | 188 | switch r { 189 | case commentStart: 190 | lx.push(lexTop) 191 | return lexCommentStart 192 | case tableStart: 193 | return lexTableStart 194 | case eof: 195 | if lx.pos > lx.start { 196 | return lx.errorf("Unexpected EOF.") 197 | } 198 | lx.emit(itemEOF) 199 | return nil 200 | } 201 | 202 | // At this point, the only valid item can be a key, so we back up 203 | // and let the key lexer do the rest. 204 | lx.backup() 205 | lx.push(lexTopEnd) 206 | return lexKeyStart 207 | } 208 | 209 | // lexTopEnd is entered whenever a top-level item has been consumed. (A value 210 | // or a table.) It must see only whitespace, and will turn back to lexTop 211 | // upon a new line. If it sees EOF, it will quit the lexer successfully. 212 | func lexTopEnd(lx *lexer) stateFn { 213 | r := lx.next() 214 | switch { 215 | case r == commentStart: 216 | // a comment will read to a new line for us. 217 | lx.push(lexTop) 218 | return lexCommentStart 219 | case isWhitespace(r): 220 | return lexTopEnd 221 | case isNL(r): 222 | lx.ignore() 223 | return lexTop 224 | case r == eof: 225 | lx.ignore() 226 | return lexTop 227 | } 228 | return lx.errorf("Expected a top-level item to end with a new line, "+ 229 | "comment or EOF, but got %q instead.", r) 230 | } 231 | 232 | // lexTable lexes the beginning of a table. Namely, it makes sure that 233 | // it starts with a character other than '.' and ']'. 234 | // It assumes that '[' has already been consumed. 235 | // It also handles the case that this is an item in an array of tables. 236 | // e.g., '[[name]]'. 237 | func lexTableStart(lx *lexer) stateFn { 238 | if lx.peek() == arrayTableStart { 239 | lx.next() 240 | lx.emit(itemArrayTableStart) 241 | lx.push(lexArrayTableEnd) 242 | } else { 243 | lx.emit(itemTableStart) 244 | lx.push(lexTableEnd) 245 | } 246 | return lexTableNameStart 247 | } 248 | 249 | func lexTableEnd(lx *lexer) stateFn { 250 | lx.emit(itemTableEnd) 251 | return lexTopEnd 252 | } 253 | 254 | func lexArrayTableEnd(lx *lexer) stateFn { 255 | if r := lx.next(); r != arrayTableEnd { 256 | return lx.errorf("Expected end of table array name delimiter %q, "+ 257 | "but got %q instead.", arrayTableEnd, r) 258 | } 259 | lx.emit(itemArrayTableEnd) 260 | return lexTopEnd 261 | } 262 | 263 | func lexTableNameStart(lx *lexer) stateFn { 264 | switch r := lx.peek(); { 265 | case r == tableEnd || r == eof: 266 | return lx.errorf("Unexpected end of table name. (Table names cannot " + 267 | "be empty.)") 268 | case r == tableSep: 269 | return lx.errorf("Unexpected table separator. (Table names cannot " + 270 | "be empty.)") 271 | case r == stringStart || r == rawStringStart: 272 | lx.ignore() 273 | lx.push(lexTableNameEnd) 274 | return lexValue // reuse string lexing 275 | case isWhitespace(r): 276 | return lexTableNameStart 277 | default: 278 | return lexBareTableName 279 | } 280 | } 281 | 282 | // lexTableName lexes the name of a table. It assumes that at least one 283 | // valid character for the table has already been read. 284 | func lexBareTableName(lx *lexer) stateFn { 285 | switch r := lx.next(); { 286 | case isBareKeyChar(r): 287 | return lexBareTableName 288 | case r == tableSep || r == tableEnd: 289 | lx.backup() 290 | lx.emitTrim(itemText) 291 | return lexTableNameEnd 292 | default: 293 | return lx.errorf("Bare keys cannot contain %q.", r) 294 | } 295 | } 296 | 297 | // lexTableNameEnd reads the end of a piece of a table name, optionally 298 | // consuming whitespace. 299 | func lexTableNameEnd(lx *lexer) stateFn { 300 | switch r := lx.next(); { 301 | case isWhitespace(r): 302 | return lexTableNameEnd 303 | case r == tableSep: 304 | lx.ignore() 305 | return lexTableNameStart 306 | case r == tableEnd: 307 | return lx.pop() 308 | default: 309 | return lx.errorf("Expected '.' or ']' to end table name, but got %q "+ 310 | "instead.", r) 311 | } 312 | } 313 | 314 | // lexKeyStart consumes a key name up until the first non-whitespace character. 315 | // lexKeyStart will ignore whitespace. 316 | func lexKeyStart(lx *lexer) stateFn { 317 | r := lx.peek() 318 | switch { 319 | case r == keySep: 320 | return lx.errorf("Unexpected key separator %q.", keySep) 321 | case isWhitespace(r) || isNL(r): 322 | lx.next() 323 | return lexSkip(lx, lexKeyStart) 324 | case r == stringStart || r == rawStringStart: 325 | lx.ignore() 326 | lx.emit(itemKeyStart) 327 | lx.push(lexKeyEnd) 328 | return lexValue // reuse string lexing 329 | default: 330 | lx.ignore() 331 | lx.emit(itemKeyStart) 332 | return lexBareKey 333 | } 334 | } 335 | 336 | // lexBareKey consumes the text of a bare key. Assumes that the first character 337 | // (which is not whitespace) has not yet been consumed. 338 | func lexBareKey(lx *lexer) stateFn { 339 | switch r := lx.next(); { 340 | case isBareKeyChar(r): 341 | return lexBareKey 342 | case isWhitespace(r): 343 | lx.emitTrim(itemText) 344 | return lexKeyEnd 345 | case r == keySep: 346 | lx.backup() 347 | lx.emitTrim(itemText) 348 | return lexKeyEnd 349 | default: 350 | return lx.errorf("Bare keys cannot contain %q.", r) 351 | } 352 | } 353 | 354 | // lexKeyEnd consumes the end of a key and trims whitespace (up to the key 355 | // separator). 356 | func lexKeyEnd(lx *lexer) stateFn { 357 | switch r := lx.next(); { 358 | case r == keySep: 359 | return lexSkip(lx, lexValue) 360 | case isWhitespace(r): 361 | return lexSkip(lx, lexKeyEnd) 362 | default: 363 | return lx.errorf("Expected key separator %q, but got %q instead.", 364 | keySep, r) 365 | } 366 | } 367 | 368 | // lexValue starts the consumption of a value anywhere a value is expected. 369 | // lexValue will ignore whitespace. 370 | // After a value is lexed, the last state on the next is popped and returned. 371 | func lexValue(lx *lexer) stateFn { 372 | // We allow whitespace to precede a value, but NOT new lines. 373 | // In array syntax, the array states are responsible for ignoring new 374 | // lines. 375 | r := lx.next() 376 | if isWhitespace(r) { 377 | return lexSkip(lx, lexValue) 378 | } 379 | 380 | switch { 381 | case r == arrayStart: 382 | lx.ignore() 383 | lx.emit(itemArray) 384 | return lexArrayValue 385 | case r == stringStart: 386 | if lx.accept(stringStart) { 387 | if lx.accept(stringStart) { 388 | lx.ignore() // Ignore """ 389 | return lexMultilineString 390 | } 391 | lx.backup() 392 | } 393 | lx.ignore() // ignore the '"' 394 | return lexString 395 | case r == rawStringStart: 396 | if lx.accept(rawStringStart) { 397 | if lx.accept(rawStringStart) { 398 | lx.ignore() // Ignore """ 399 | return lexMultilineRawString 400 | } 401 | lx.backup() 402 | } 403 | lx.ignore() // ignore the "'" 404 | return lexRawString 405 | case r == 't': 406 | return lexTrue 407 | case r == 'f': 408 | return lexFalse 409 | case r == '-': 410 | return lexNumberStart 411 | case isDigit(r): 412 | lx.backup() // avoid an extra state and use the same as above 413 | return lexNumberOrDateStart 414 | case r == '.': // special error case, be kind to users 415 | return lx.errorf("Floats must start with a digit, not '.'.") 416 | } 417 | return lx.errorf("Expected value but found %q instead.", r) 418 | } 419 | 420 | // lexArrayValue consumes one value in an array. It assumes that '[' or ',' 421 | // have already been consumed. All whitespace and new lines are ignored. 422 | func lexArrayValue(lx *lexer) stateFn { 423 | r := lx.next() 424 | switch { 425 | case isWhitespace(r) || isNL(r): 426 | return lexSkip(lx, lexArrayValue) 427 | case r == commentStart: 428 | lx.push(lexArrayValue) 429 | return lexCommentStart 430 | case r == arrayValTerm: 431 | return lx.errorf("Unexpected array value terminator %q.", 432 | arrayValTerm) 433 | case r == arrayEnd: 434 | return lexArrayEnd 435 | } 436 | 437 | lx.backup() 438 | lx.push(lexArrayValueEnd) 439 | return lexValue 440 | } 441 | 442 | // lexArrayValueEnd consumes the cruft between values of an array. Namely, 443 | // it ignores whitespace and expects either a ',' or a ']'. 444 | func lexArrayValueEnd(lx *lexer) stateFn { 445 | r := lx.next() 446 | switch { 447 | case isWhitespace(r) || isNL(r): 448 | return lexSkip(lx, lexArrayValueEnd) 449 | case r == commentStart: 450 | lx.push(lexArrayValueEnd) 451 | return lexCommentStart 452 | case r == arrayValTerm: 453 | lx.ignore() 454 | return lexArrayValue // move on to the next value 455 | case r == arrayEnd: 456 | return lexArrayEnd 457 | } 458 | return lx.errorf("Expected an array value terminator %q or an array "+ 459 | "terminator %q, but got %q instead.", arrayValTerm, arrayEnd, r) 460 | } 461 | 462 | // lexArrayEnd finishes the lexing of an array. It assumes that a ']' has 463 | // just been consumed. 464 | func lexArrayEnd(lx *lexer) stateFn { 465 | lx.ignore() 466 | lx.emit(itemArrayEnd) 467 | return lx.pop() 468 | } 469 | 470 | // lexString consumes the inner contents of a string. It assumes that the 471 | // beginning '"' has already been consumed and ignored. 472 | func lexString(lx *lexer) stateFn { 473 | r := lx.next() 474 | switch { 475 | case isNL(r): 476 | return lx.errorf("Strings cannot contain new lines.") 477 | case r == '\\': 478 | lx.push(lexString) 479 | return lexStringEscape 480 | case r == stringEnd: 481 | lx.backup() 482 | lx.emit(itemString) 483 | lx.next() 484 | lx.ignore() 485 | return lx.pop() 486 | } 487 | return lexString 488 | } 489 | 490 | // lexMultilineString consumes the inner contents of a string. It assumes that 491 | // the beginning '"""' has already been consumed and ignored. 492 | func lexMultilineString(lx *lexer) stateFn { 493 | r := lx.next() 494 | switch { 495 | case r == '\\': 496 | return lexMultilineStringEscape 497 | case r == stringEnd: 498 | if lx.accept(stringEnd) { 499 | if lx.accept(stringEnd) { 500 | lx.backup() 501 | lx.backup() 502 | lx.backup() 503 | lx.emit(itemMultilineString) 504 | lx.next() 505 | lx.next() 506 | lx.next() 507 | lx.ignore() 508 | return lx.pop() 509 | } 510 | lx.backup() 511 | } 512 | } 513 | return lexMultilineString 514 | } 515 | 516 | // lexRawString consumes a raw string. Nothing can be escaped in such a string. 517 | // It assumes that the beginning "'" has already been consumed and ignored. 518 | func lexRawString(lx *lexer) stateFn { 519 | r := lx.next() 520 | switch { 521 | case isNL(r): 522 | return lx.errorf("Strings cannot contain new lines.") 523 | case r == rawStringEnd: 524 | lx.backup() 525 | lx.emit(itemRawString) 526 | lx.next() 527 | lx.ignore() 528 | return lx.pop() 529 | } 530 | return lexRawString 531 | } 532 | 533 | // lexMultilineRawString consumes a raw string. Nothing can be escaped in such 534 | // a string. It assumes that the beginning "'" has already been consumed and 535 | // ignored. 536 | func lexMultilineRawString(lx *lexer) stateFn { 537 | r := lx.next() 538 | switch { 539 | case r == rawStringEnd: 540 | if lx.accept(rawStringEnd) { 541 | if lx.accept(rawStringEnd) { 542 | lx.backup() 543 | lx.backup() 544 | lx.backup() 545 | lx.emit(itemRawMultilineString) 546 | lx.next() 547 | lx.next() 548 | lx.next() 549 | lx.ignore() 550 | return lx.pop() 551 | } 552 | lx.backup() 553 | } 554 | } 555 | return lexMultilineRawString 556 | } 557 | 558 | // lexMultilineStringEscape consumes an escaped character. It assumes that the 559 | // preceding '\\' has already been consumed. 560 | func lexMultilineStringEscape(lx *lexer) stateFn { 561 | // Handle the special case first: 562 | if isNL(lx.next()) { 563 | lx.next() 564 | return lexMultilineString 565 | } else { 566 | lx.backup() 567 | lx.push(lexMultilineString) 568 | return lexStringEscape(lx) 569 | } 570 | } 571 | 572 | func lexStringEscape(lx *lexer) stateFn { 573 | r := lx.next() 574 | switch r { 575 | case 'b': 576 | fallthrough 577 | case 't': 578 | fallthrough 579 | case 'n': 580 | fallthrough 581 | case 'f': 582 | fallthrough 583 | case 'r': 584 | fallthrough 585 | case '"': 586 | fallthrough 587 | case '\\': 588 | return lx.pop() 589 | case 'u': 590 | return lexShortUnicodeEscape 591 | case 'U': 592 | return lexLongUnicodeEscape 593 | } 594 | return lx.errorf("Invalid escape character %q. Only the following "+ 595 | "escape characters are allowed: "+ 596 | "\\b, \\t, \\n, \\f, \\r, \\\", \\/, \\\\, "+ 597 | "\\uXXXX and \\UXXXXXXXX.", r) 598 | } 599 | 600 | func lexShortUnicodeEscape(lx *lexer) stateFn { 601 | var r rune 602 | for i := 0; i < 4; i++ { 603 | r = lx.next() 604 | if !isHexadecimal(r) { 605 | return lx.errorf("Expected four hexadecimal digits after '\\u', "+ 606 | "but got '%s' instead.", lx.current()) 607 | } 608 | } 609 | return lx.pop() 610 | } 611 | 612 | func lexLongUnicodeEscape(lx *lexer) stateFn { 613 | var r rune 614 | for i := 0; i < 8; i++ { 615 | r = lx.next() 616 | if !isHexadecimal(r) { 617 | return lx.errorf("Expected eight hexadecimal digits after '\\U', "+ 618 | "but got '%s' instead.", lx.current()) 619 | } 620 | } 621 | return lx.pop() 622 | } 623 | 624 | // lexNumberOrDateStart consumes either a (positive) integer, float or 625 | // datetime. It assumes that NO negative sign has been consumed. 626 | func lexNumberOrDateStart(lx *lexer) stateFn { 627 | r := lx.next() 628 | if !isDigit(r) { 629 | if r == '.' { 630 | return lx.errorf("Floats must start with a digit, not '.'.") 631 | } else { 632 | return lx.errorf("Expected a digit but got %q.", r) 633 | } 634 | } 635 | return lexNumberOrDate 636 | } 637 | 638 | // lexNumberOrDate consumes either a (positive) integer, float or datetime. 639 | func lexNumberOrDate(lx *lexer) stateFn { 640 | r := lx.next() 641 | switch { 642 | case r == '-': 643 | if lx.pos-lx.start != 5 { 644 | return lx.errorf("All ISO8601 dates must be in full Zulu form.") 645 | } 646 | return lexDateAfterYear 647 | case isDigit(r): 648 | return lexNumberOrDate 649 | case r == '.': 650 | return lexFloatStart 651 | } 652 | 653 | lx.backup() 654 | lx.emit(itemInteger) 655 | return lx.pop() 656 | } 657 | 658 | // lexDateAfterYear consumes a full Zulu Datetime in ISO8601 format. 659 | // It assumes that "YYYY-" has already been consumed. 660 | func lexDateAfterYear(lx *lexer) stateFn { 661 | formats := []rune{ 662 | // digits are '0'. 663 | // everything else is direct equality. 664 | '0', '0', '-', '0', '0', 665 | 'T', 666 | '0', '0', ':', '0', '0', ':', '0', '0', 667 | 'Z', 668 | } 669 | for _, f := range formats { 670 | r := lx.next() 671 | if f == '0' { 672 | if !isDigit(r) { 673 | return lx.errorf("Expected digit in ISO8601 datetime, "+ 674 | "but found %q instead.", r) 675 | } 676 | } else if f != r { 677 | return lx.errorf("Expected %q in ISO8601 datetime, "+ 678 | "but found %q instead.", f, r) 679 | } 680 | } 681 | lx.emit(itemDatetime) 682 | return lx.pop() 683 | } 684 | 685 | // lexNumberStart consumes either an integer or a float. It assumes that 686 | // a negative sign has already been read, but that *no* digits have been 687 | // consumed. lexNumberStart will move to the appropriate integer or float 688 | // states. 689 | func lexNumberStart(lx *lexer) stateFn { 690 | // we MUST see a digit. Even floats have to start with a digit. 691 | r := lx.next() 692 | if !isDigit(r) { 693 | if r == '.' { 694 | return lx.errorf("Floats must start with a digit, not '.'.") 695 | } else { 696 | return lx.errorf("Expected a digit but got %q.", r) 697 | } 698 | } 699 | return lexNumber 700 | } 701 | 702 | // lexNumber consumes an integer or a float after seeing the first digit. 703 | func lexNumber(lx *lexer) stateFn { 704 | r := lx.next() 705 | switch { 706 | case isDigit(r): 707 | return lexNumber 708 | case r == '.': 709 | return lexFloatStart 710 | } 711 | 712 | lx.backup() 713 | lx.emit(itemInteger) 714 | return lx.pop() 715 | } 716 | 717 | // lexFloatStart starts the consumption of digits of a float after a '.'. 718 | // Namely, at least one digit is required. 719 | func lexFloatStart(lx *lexer) stateFn { 720 | r := lx.next() 721 | if !isDigit(r) { 722 | return lx.errorf("Floats must have a digit after the '.', but got "+ 723 | "%q instead.", r) 724 | } 725 | return lexFloat 726 | } 727 | 728 | // lexFloat consumes the digits of a float after a '.'. 729 | // Assumes that one digit has been consumed after a '.' already. 730 | func lexFloat(lx *lexer) stateFn { 731 | r := lx.next() 732 | if isDigit(r) { 733 | return lexFloat 734 | } 735 | 736 | lx.backup() 737 | lx.emit(itemFloat) 738 | return lx.pop() 739 | } 740 | 741 | // lexConst consumes the s[1:] in s. It assumes that s[0] has already been 742 | // consumed. 743 | func lexConst(lx *lexer, s string) stateFn { 744 | for i := range s[1:] { 745 | if r := lx.next(); r != rune(s[i+1]) { 746 | return lx.errorf("Expected %q, but found %q instead.", s[:i+1], 747 | s[:i]+string(r)) 748 | } 749 | } 750 | return nil 751 | } 752 | 753 | // lexTrue consumes the "rue" in "true". It assumes that 't' has already 754 | // been consumed. 755 | func lexTrue(lx *lexer) stateFn { 756 | if fn := lexConst(lx, "true"); fn != nil { 757 | return fn 758 | } 759 | lx.emit(itemBool) 760 | return lx.pop() 761 | } 762 | 763 | // lexFalse consumes the "alse" in "false". It assumes that 'f' has already 764 | // been consumed. 765 | func lexFalse(lx *lexer) stateFn { 766 | if fn := lexConst(lx, "false"); fn != nil { 767 | return fn 768 | } 769 | lx.emit(itemBool) 770 | return lx.pop() 771 | } 772 | 773 | // lexCommentStart begins the lexing of a comment. It will emit 774 | // itemCommentStart and consume no characters, passing control to lexComment. 775 | func lexCommentStart(lx *lexer) stateFn { 776 | lx.ignore() 777 | lx.emit(itemCommentStart) 778 | return lexComment 779 | } 780 | 781 | // lexComment lexes an entire comment. It assumes that '#' has been consumed. 782 | // It will consume *up to* the first new line character, and pass control 783 | // back to the last state on the stack. 784 | func lexComment(lx *lexer) stateFn { 785 | r := lx.peek() 786 | if isNL(r) || r == eof { 787 | lx.emit(itemText) 788 | return lx.pop() 789 | } 790 | lx.next() 791 | return lexComment 792 | } 793 | 794 | // lexSkip ignores all slurped input and moves on to the next state. 795 | func lexSkip(lx *lexer, nextState stateFn) stateFn { 796 | return func(lx *lexer) stateFn { 797 | lx.ignore() 798 | return nextState 799 | } 800 | } 801 | 802 | // isWhitespace returns true if `r` is a whitespace character according 803 | // to the spec. 804 | func isWhitespace(r rune) bool { 805 | return r == '\t' || r == ' ' 806 | } 807 | 808 | func isNL(r rune) bool { 809 | return r == '\n' || r == '\r' 810 | } 811 | 812 | func isDigit(r rune) bool { 813 | return r >= '0' && r <= '9' 814 | } 815 | 816 | func isHexadecimal(r rune) bool { 817 | return (r >= '0' && r <= '9') || 818 | (r >= 'a' && r <= 'f') || 819 | (r >= 'A' && r <= 'F') 820 | } 821 | 822 | func isBareKeyChar(r rune) bool { 823 | return (r >= 'A' && r <= 'Z') || 824 | (r >= 'a' && r <= 'z') || 825 | (r >= '0' && r <= '9') || 826 | r == '_' || 827 | r == '-' 828 | } 829 | 830 | func (itype itemType) String() string { 831 | switch itype { 832 | case itemError: 833 | return "Error" 834 | case itemNIL: 835 | return "NIL" 836 | case itemEOF: 837 | return "EOF" 838 | case itemText: 839 | return "Text" 840 | case itemString: 841 | return "String" 842 | case itemRawString: 843 | return "String" 844 | case itemMultilineString: 845 | return "String" 846 | case itemRawMultilineString: 847 | return "String" 848 | case itemBool: 849 | return "Bool" 850 | case itemInteger: 851 | return "Integer" 852 | case itemFloat: 853 | return "Float" 854 | case itemDatetime: 855 | return "DateTime" 856 | case itemTableStart: 857 | return "TableStart" 858 | case itemTableEnd: 859 | return "TableEnd" 860 | case itemKeyStart: 861 | return "KeyStart" 862 | case itemArray: 863 | return "Array" 864 | case itemArrayEnd: 865 | return "ArrayEnd" 866 | case itemCommentStart: 867 | return "CommentStart" 868 | } 869 | panic(fmt.Sprintf("BUG: Unknown type '%d'.", int(itype))) 870 | } 871 | 872 | func (item item) String() string { 873 | return fmt.Sprintf("(%s, %s)", item.typ.String(), item.val) 874 | } 875 | -------------------------------------------------------------------------------- /_third_party/github.com/BurntSushi/toml/parse.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strconv" 7 | "strings" 8 | "time" 9 | "unicode" 10 | "unicode/utf8" 11 | ) 12 | 13 | type parser struct { 14 | mapping map[string]interface{} 15 | types map[string]tomlType 16 | lx *lexer 17 | 18 | // A list of keys in the order that they appear in the TOML data. 19 | ordered []Key 20 | 21 | // the full key for the current hash in scope 22 | context Key 23 | 24 | // the base key name for everything except hashes 25 | currentKey string 26 | 27 | // rough approximation of line number 28 | approxLine int 29 | 30 | // A map of 'key.group.names' to whether they were created implicitly. 31 | implicits map[string]bool 32 | } 33 | 34 | type parseError string 35 | 36 | func (pe parseError) Error() string { 37 | return string(pe) 38 | } 39 | 40 | func parse(data string) (p *parser, err error) { 41 | defer func() { 42 | if r := recover(); r != nil { 43 | var ok bool 44 | if err, ok = r.(parseError); ok { 45 | return 46 | } 47 | panic(r) 48 | } 49 | }() 50 | 51 | p = &parser{ 52 | mapping: make(map[string]interface{}), 53 | types: make(map[string]tomlType), 54 | lx: lex(data), 55 | ordered: make([]Key, 0), 56 | implicits: make(map[string]bool), 57 | } 58 | for { 59 | item := p.next() 60 | if item.typ == itemEOF { 61 | break 62 | } 63 | p.topLevel(item) 64 | } 65 | 66 | return p, nil 67 | } 68 | 69 | func (p *parser) panicf(format string, v ...interface{}) { 70 | msg := fmt.Sprintf("Near line %d (last key parsed '%s'): %s", 71 | p.approxLine, p.current(), fmt.Sprintf(format, v...)) 72 | panic(parseError(msg)) 73 | } 74 | 75 | func (p *parser) next() item { 76 | it := p.lx.nextItem() 77 | if it.typ == itemError { 78 | p.panicf("%s", it.val) 79 | } 80 | return it 81 | } 82 | 83 | func (p *parser) bug(format string, v ...interface{}) { 84 | log.Fatalf("BUG: %s\n\n", fmt.Sprintf(format, v...)) 85 | } 86 | 87 | func (p *parser) expect(typ itemType) item { 88 | it := p.next() 89 | p.assertEqual(typ, it.typ) 90 | return it 91 | } 92 | 93 | func (p *parser) assertEqual(expected, got itemType) { 94 | if expected != got { 95 | p.bug("Expected '%s' but got '%s'.", expected, got) 96 | } 97 | } 98 | 99 | func (p *parser) topLevel(item item) { 100 | switch item.typ { 101 | case itemCommentStart: 102 | p.approxLine = item.line 103 | p.expect(itemText) 104 | case itemTableStart: 105 | kg := p.next() 106 | p.approxLine = kg.line 107 | 108 | var key Key 109 | for ; kg.typ != itemTableEnd && kg.typ != itemEOF; kg = p.next() { 110 | key = append(key, p.keyString(kg)) 111 | } 112 | p.assertEqual(itemTableEnd, kg.typ) 113 | 114 | p.establishContext(key, false) 115 | p.setType("", tomlHash) 116 | p.ordered = append(p.ordered, key) 117 | case itemArrayTableStart: 118 | kg := p.next() 119 | p.approxLine = kg.line 120 | 121 | var key Key 122 | for ; kg.typ != itemArrayTableEnd && kg.typ != itemEOF; kg = p.next() { 123 | key = append(key, p.keyString(kg)) 124 | } 125 | p.assertEqual(itemArrayTableEnd, kg.typ) 126 | 127 | p.establishContext(key, true) 128 | p.setType("", tomlArrayHash) 129 | p.ordered = append(p.ordered, key) 130 | case itemKeyStart: 131 | kname := p.next() 132 | p.approxLine = kname.line 133 | p.currentKey = p.keyString(kname) 134 | 135 | val, typ := p.value(p.next()) 136 | p.setValue(p.currentKey, val) 137 | p.setType(p.currentKey, typ) 138 | p.ordered = append(p.ordered, p.context.add(p.currentKey)) 139 | p.currentKey = "" 140 | default: 141 | p.bug("Unexpected type at top level: %s", item.typ) 142 | } 143 | } 144 | 145 | // Gets a string for a key (or part of a key in a table name). 146 | func (p *parser) keyString(it item) string { 147 | switch it.typ { 148 | case itemText: 149 | return it.val 150 | case itemString, itemMultilineString, 151 | itemRawString, itemRawMultilineString: 152 | s, _ := p.value(it) 153 | return s.(string) 154 | default: 155 | p.bug("Unexpected key type: %s", it.typ) 156 | panic("unreachable") 157 | } 158 | } 159 | 160 | // value translates an expected value from the lexer into a Go value wrapped 161 | // as an empty interface. 162 | func (p *parser) value(it item) (interface{}, tomlType) { 163 | switch it.typ { 164 | case itemString: 165 | return p.replaceEscapes(it.val), p.typeOfPrimitive(it) 166 | case itemMultilineString: 167 | trimmed := stripFirstNewline(stripEscapedWhitespace(it.val)) 168 | return p.replaceEscapes(trimmed), p.typeOfPrimitive(it) 169 | case itemRawString: 170 | return it.val, p.typeOfPrimitive(it) 171 | case itemRawMultilineString: 172 | return stripFirstNewline(it.val), p.typeOfPrimitive(it) 173 | case itemBool: 174 | switch it.val { 175 | case "true": 176 | return true, p.typeOfPrimitive(it) 177 | case "false": 178 | return false, p.typeOfPrimitive(it) 179 | } 180 | p.bug("Expected boolean value, but got '%s'.", it.val) 181 | case itemInteger: 182 | num, err := strconv.ParseInt(it.val, 10, 64) 183 | if err != nil { 184 | // See comment below for floats describing why we make a 185 | // distinction between a bug and a user error. 186 | if e, ok := err.(*strconv.NumError); ok && 187 | e.Err == strconv.ErrRange { 188 | 189 | p.panicf("Integer '%s' is out of the range of 64-bit "+ 190 | "signed integers.", it.val) 191 | } else { 192 | p.bug("Expected integer value, but got '%s'.", it.val) 193 | } 194 | } 195 | return num, p.typeOfPrimitive(it) 196 | case itemFloat: 197 | num, err := strconv.ParseFloat(it.val, 64) 198 | if err != nil { 199 | // Distinguish float values. Normally, it'd be a bug if the lexer 200 | // provides an invalid float, but it's possible that the float is 201 | // out of range of valid values (which the lexer cannot determine). 202 | // So mark the former as a bug but the latter as a legitimate user 203 | // error. 204 | // 205 | // This is also true for integers. 206 | if e, ok := err.(*strconv.NumError); ok && 207 | e.Err == strconv.ErrRange { 208 | 209 | p.panicf("Float '%s' is out of the range of 64-bit "+ 210 | "IEEE-754 floating-point numbers.", it.val) 211 | } else { 212 | p.bug("Expected float value, but got '%s'.", it.val) 213 | } 214 | } 215 | return num, p.typeOfPrimitive(it) 216 | case itemDatetime: 217 | t, err := time.Parse("2006-01-02T15:04:05Z", it.val) 218 | if err != nil { 219 | p.bug("Expected Zulu formatted DateTime, but got '%s'.", it.val) 220 | } 221 | return t, p.typeOfPrimitive(it) 222 | case itemArray: 223 | array := make([]interface{}, 0) 224 | types := make([]tomlType, 0) 225 | 226 | for it = p.next(); it.typ != itemArrayEnd; it = p.next() { 227 | if it.typ == itemCommentStart { 228 | p.expect(itemText) 229 | continue 230 | } 231 | 232 | val, typ := p.value(it) 233 | array = append(array, val) 234 | types = append(types, typ) 235 | } 236 | return array, p.typeOfArray(types) 237 | } 238 | p.bug("Unexpected value type: %s", it.typ) 239 | panic("unreachable") 240 | } 241 | 242 | // establishContext sets the current context of the parser, 243 | // where the context is either a hash or an array of hashes. Which one is 244 | // set depends on the value of the `array` parameter. 245 | // 246 | // Establishing the context also makes sure that the key isn't a duplicate, and 247 | // will create implicit hashes automatically. 248 | func (p *parser) establishContext(key Key, array bool) { 249 | var ok bool 250 | 251 | // Always start at the top level and drill down for our context. 252 | hashContext := p.mapping 253 | keyContext := make(Key, 0) 254 | 255 | // We only need implicit hashes for key[0:-1] 256 | for _, k := range key[0 : len(key)-1] { 257 | _, ok = hashContext[k] 258 | keyContext = append(keyContext, k) 259 | 260 | // No key? Make an implicit hash and move on. 261 | if !ok { 262 | p.addImplicit(keyContext) 263 | hashContext[k] = make(map[string]interface{}) 264 | } 265 | 266 | // If the hash context is actually an array of tables, then set 267 | // the hash context to the last element in that array. 268 | // 269 | // Otherwise, it better be a table, since this MUST be a key group (by 270 | // virtue of it not being the last element in a key). 271 | switch t := hashContext[k].(type) { 272 | case []map[string]interface{}: 273 | hashContext = t[len(t)-1] 274 | case map[string]interface{}: 275 | hashContext = t 276 | default: 277 | p.panicf("Key '%s' was already created as a hash.", keyContext) 278 | } 279 | } 280 | 281 | p.context = keyContext 282 | if array { 283 | // If this is the first element for this array, then allocate a new 284 | // list of tables for it. 285 | k := key[len(key)-1] 286 | if _, ok := hashContext[k]; !ok { 287 | hashContext[k] = make([]map[string]interface{}, 0, 5) 288 | } 289 | 290 | // Add a new table. But make sure the key hasn't already been used 291 | // for something else. 292 | if hash, ok := hashContext[k].([]map[string]interface{}); ok { 293 | hashContext[k] = append(hash, make(map[string]interface{})) 294 | } else { 295 | p.panicf("Key '%s' was already created and cannot be used as "+ 296 | "an array.", keyContext) 297 | } 298 | } else { 299 | p.setValue(key[len(key)-1], make(map[string]interface{})) 300 | } 301 | p.context = append(p.context, key[len(key)-1]) 302 | } 303 | 304 | // setValue sets the given key to the given value in the current context. 305 | // It will make sure that the key hasn't already been defined, account for 306 | // implicit key groups. 307 | func (p *parser) setValue(key string, value interface{}) { 308 | var tmpHash interface{} 309 | var ok bool 310 | 311 | hash := p.mapping 312 | keyContext := make(Key, 0) 313 | for _, k := range p.context { 314 | keyContext = append(keyContext, k) 315 | if tmpHash, ok = hash[k]; !ok { 316 | p.bug("Context for key '%s' has not been established.", keyContext) 317 | } 318 | switch t := tmpHash.(type) { 319 | case []map[string]interface{}: 320 | // The context is a table of hashes. Pick the most recent table 321 | // defined as the current hash. 322 | hash = t[len(t)-1] 323 | case map[string]interface{}: 324 | hash = t 325 | default: 326 | p.bug("Expected hash to have type 'map[string]interface{}', but "+ 327 | "it has '%T' instead.", tmpHash) 328 | } 329 | } 330 | keyContext = append(keyContext, key) 331 | 332 | if _, ok := hash[key]; ok { 333 | // Typically, if the given key has already been set, then we have 334 | // to raise an error since duplicate keys are disallowed. However, 335 | // it's possible that a key was previously defined implicitly. In this 336 | // case, it is allowed to be redefined concretely. (See the 337 | // `tests/valid/implicit-and-explicit-after.toml` test in `toml-test`.) 338 | // 339 | // But we have to make sure to stop marking it as an implicit. (So that 340 | // another redefinition provokes an error.) 341 | // 342 | // Note that since it has already been defined (as a hash), we don't 343 | // want to overwrite it. So our business is done. 344 | if p.isImplicit(keyContext) { 345 | p.removeImplicit(keyContext) 346 | return 347 | } 348 | 349 | // Otherwise, we have a concrete key trying to override a previous 350 | // key, which is *always* wrong. 351 | p.panicf("Key '%s' has already been defined.", keyContext) 352 | } 353 | hash[key] = value 354 | } 355 | 356 | // setType sets the type of a particular value at a given key. 357 | // It should be called immediately AFTER setValue. 358 | // 359 | // Note that if `key` is empty, then the type given will be applied to the 360 | // current context (which is either a table or an array of tables). 361 | func (p *parser) setType(key string, typ tomlType) { 362 | keyContext := make(Key, 0, len(p.context)+1) 363 | for _, k := range p.context { 364 | keyContext = append(keyContext, k) 365 | } 366 | if len(key) > 0 { // allow type setting for hashes 367 | keyContext = append(keyContext, key) 368 | } 369 | p.types[keyContext.String()] = typ 370 | } 371 | 372 | // addImplicit sets the given Key as having been created implicitly. 373 | func (p *parser) addImplicit(key Key) { 374 | p.implicits[key.String()] = true 375 | } 376 | 377 | // removeImplicit stops tagging the given key as having been implicitly 378 | // created. 379 | func (p *parser) removeImplicit(key Key) { 380 | p.implicits[key.String()] = false 381 | } 382 | 383 | // isImplicit returns true if the key group pointed to by the key was created 384 | // implicitly. 385 | func (p *parser) isImplicit(key Key) bool { 386 | return p.implicits[key.String()] 387 | } 388 | 389 | // current returns the full key name of the current context. 390 | func (p *parser) current() string { 391 | if len(p.currentKey) == 0 { 392 | return p.context.String() 393 | } 394 | if len(p.context) == 0 { 395 | return p.currentKey 396 | } 397 | return fmt.Sprintf("%s.%s", p.context, p.currentKey) 398 | } 399 | 400 | func stripFirstNewline(s string) string { 401 | if len(s) == 0 || s[0] != '\n' { 402 | return s 403 | } 404 | return s[1:] 405 | } 406 | 407 | func stripEscapedWhitespace(s string) string { 408 | esc := strings.Split(s, "\\\n") 409 | if len(esc) > 1 { 410 | for i := 1; i < len(esc); i++ { 411 | esc[i] = strings.TrimLeftFunc(esc[i], unicode.IsSpace) 412 | } 413 | } 414 | return strings.Join(esc, "") 415 | } 416 | 417 | func (p *parser) replaceEscapes(str string) string { 418 | var replaced []rune 419 | s := []byte(str) 420 | r := 0 421 | for r < len(s) { 422 | if s[r] != '\\' { 423 | c, size := utf8.DecodeRune(s[r:]) 424 | r += size 425 | replaced = append(replaced, c) 426 | continue 427 | } 428 | r += 1 429 | if r >= len(s) { 430 | p.bug("Escape sequence at end of string.") 431 | return "" 432 | } 433 | switch s[r] { 434 | default: 435 | p.bug("Expected valid escape code after \\, but got %q.", s[r]) 436 | return "" 437 | case 'b': 438 | replaced = append(replaced, rune(0x0008)) 439 | r += 1 440 | case 't': 441 | replaced = append(replaced, rune(0x0009)) 442 | r += 1 443 | case 'n': 444 | replaced = append(replaced, rune(0x000A)) 445 | r += 1 446 | case 'f': 447 | replaced = append(replaced, rune(0x000C)) 448 | r += 1 449 | case 'r': 450 | replaced = append(replaced, rune(0x000D)) 451 | r += 1 452 | case '"': 453 | replaced = append(replaced, rune(0x0022)) 454 | r += 1 455 | case '\\': 456 | replaced = append(replaced, rune(0x005C)) 457 | r += 1 458 | case 'u': 459 | // At this point, we know we have a Unicode escape of the form 460 | // `uXXXX` at [r, r+5). (Because the lexer guarantees this 461 | // for us.) 462 | escaped := p.asciiEscapeToUnicode(s[r+1 : r+5]) 463 | replaced = append(replaced, escaped) 464 | r += 5 465 | case 'U': 466 | // At this point, we know we have a Unicode escape of the form 467 | // `uXXXX` at [r, r+9). (Because the lexer guarantees this 468 | // for us.) 469 | escaped := p.asciiEscapeToUnicode(s[r+1 : r+9]) 470 | replaced = append(replaced, escaped) 471 | r += 9 472 | } 473 | } 474 | return string(replaced) 475 | } 476 | 477 | func (p *parser) asciiEscapeToUnicode(bs []byte) rune { 478 | s := string(bs) 479 | hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32) 480 | if err != nil { 481 | p.bug("Could not parse '%s' as a hexadecimal number, but the "+ 482 | "lexer claims it's OK: %s", s, err) 483 | } 484 | 485 | // BUG(burntsushi) 486 | // I honestly don't understand how this works. I can't seem 487 | // to find a way to make this fail. I figured this would fail on invalid 488 | // UTF-8 characters like U+DCFF, but it doesn't. 489 | if !utf8.ValidString(string(rune(hex))) { 490 | p.panicf("Escaped character '\\u%s' is not valid UTF-8.", s) 491 | } 492 | return rune(hex) 493 | } 494 | 495 | func isStringType(ty itemType) bool { 496 | return ty == itemString || ty == itemMultilineString || 497 | ty == itemRawString || ty == itemRawMultilineString 498 | } 499 | -------------------------------------------------------------------------------- /_third_party/github.com/BurntSushi/toml/session.vim: -------------------------------------------------------------------------------- 1 | au BufWritePost *.go silent!make tags > /dev/null 2>&1 2 | -------------------------------------------------------------------------------- /_third_party/github.com/BurntSushi/toml/type_check.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | // tomlType represents any Go type that corresponds to a TOML type. 4 | // While the first draft of the TOML spec has a simplistic type system that 5 | // probably doesn't need this level of sophistication, we seem to be militating 6 | // toward adding real composite types. 7 | type tomlType interface { 8 | typeString() string 9 | } 10 | 11 | // typeEqual accepts any two types and returns true if they are equal. 12 | func typeEqual(t1, t2 tomlType) bool { 13 | if t1 == nil || t2 == nil { 14 | return false 15 | } 16 | return t1.typeString() == t2.typeString() 17 | } 18 | 19 | func typeIsHash(t tomlType) bool { 20 | return typeEqual(t, tomlHash) || typeEqual(t, tomlArrayHash) 21 | } 22 | 23 | type tomlBaseType string 24 | 25 | func (btype tomlBaseType) typeString() string { 26 | return string(btype) 27 | } 28 | 29 | func (btype tomlBaseType) String() string { 30 | return btype.typeString() 31 | } 32 | 33 | var ( 34 | tomlInteger tomlBaseType = "Integer" 35 | tomlFloat tomlBaseType = "Float" 36 | tomlDatetime tomlBaseType = "Datetime" 37 | tomlString tomlBaseType = "String" 38 | tomlBool tomlBaseType = "Bool" 39 | tomlArray tomlBaseType = "Array" 40 | tomlHash tomlBaseType = "Hash" 41 | tomlArrayHash tomlBaseType = "ArrayHash" 42 | ) 43 | 44 | // typeOfPrimitive returns a tomlType of any primitive value in TOML. 45 | // Primitive values are: Integer, Float, Datetime, String and Bool. 46 | // 47 | // Passing a lexer item other than the following will cause a BUG message 48 | // to occur: itemString, itemBool, itemInteger, itemFloat, itemDatetime. 49 | func (p *parser) typeOfPrimitive(lexItem item) tomlType { 50 | switch lexItem.typ { 51 | case itemInteger: 52 | return tomlInteger 53 | case itemFloat: 54 | return tomlFloat 55 | case itemDatetime: 56 | return tomlDatetime 57 | case itemString: 58 | return tomlString 59 | case itemMultilineString: 60 | return tomlString 61 | case itemRawString: 62 | return tomlString 63 | case itemRawMultilineString: 64 | return tomlString 65 | case itemBool: 66 | return tomlBool 67 | } 68 | p.bug("Cannot infer primitive type of lex item '%s'.", lexItem) 69 | panic("unreachable") 70 | } 71 | 72 | // typeOfArray returns a tomlType for an array given a list of types of its 73 | // values. 74 | // 75 | // In the current spec, if an array is homogeneous, then its type is always 76 | // "Array". If the array is not homogeneous, an error is generated. 77 | func (p *parser) typeOfArray(types []tomlType) tomlType { 78 | // Empty arrays are cool. 79 | if len(types) == 0 { 80 | return tomlArray 81 | } 82 | 83 | theType := types[0] 84 | for _, t := range types[1:] { 85 | if !typeEqual(theType, t) { 86 | p.panicf("Array contains values of type '%s' and '%s', but "+ 87 | "arrays must be homogeneous.", theType, t) 88 | } 89 | } 90 | return tomlArray 91 | } 92 | -------------------------------------------------------------------------------- /_third_party/github.com/BurntSushi/toml/type_fields.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | // Struct field handling is adapted from code in encoding/json: 4 | // 5 | // Copyright 2010 The Go Authors. All rights reserved. 6 | // Use of this source code is governed by a BSD-style 7 | // license that can be found in the Go distribution. 8 | 9 | import ( 10 | "reflect" 11 | "sort" 12 | "sync" 13 | ) 14 | 15 | // A field represents a single field found in a struct. 16 | type field struct { 17 | name string // the name of the field (`toml` tag included) 18 | tag bool // whether field has a `toml` tag 19 | index []int // represents the depth of an anonymous field 20 | typ reflect.Type // the type of the field 21 | } 22 | 23 | // byName sorts field by name, breaking ties with depth, 24 | // then breaking ties with "name came from toml tag", then 25 | // breaking ties with index sequence. 26 | type byName []field 27 | 28 | func (x byName) Len() int { return len(x) } 29 | 30 | func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 31 | 32 | func (x byName) Less(i, j int) bool { 33 | if x[i].name != x[j].name { 34 | return x[i].name < x[j].name 35 | } 36 | if len(x[i].index) != len(x[j].index) { 37 | return len(x[i].index) < len(x[j].index) 38 | } 39 | if x[i].tag != x[j].tag { 40 | return x[i].tag 41 | } 42 | return byIndex(x).Less(i, j) 43 | } 44 | 45 | // byIndex sorts field by index sequence. 46 | type byIndex []field 47 | 48 | func (x byIndex) Len() int { return len(x) } 49 | 50 | func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 51 | 52 | func (x byIndex) Less(i, j int) bool { 53 | for k, xik := range x[i].index { 54 | if k >= len(x[j].index) { 55 | return false 56 | } 57 | if xik != x[j].index[k] { 58 | return xik < x[j].index[k] 59 | } 60 | } 61 | return len(x[i].index) < len(x[j].index) 62 | } 63 | 64 | // typeFields returns a list of fields that TOML should recognize for the given 65 | // type. The algorithm is breadth-first search over the set of structs to 66 | // include - the top struct and then any reachable anonymous structs. 67 | func typeFields(t reflect.Type) []field { 68 | // Anonymous fields to explore at the current level and the next. 69 | current := []field{} 70 | next := []field{{typ: t}} 71 | 72 | // Count of queued names for current level and the next. 73 | count := map[reflect.Type]int{} 74 | nextCount := map[reflect.Type]int{} 75 | 76 | // Types already visited at an earlier level. 77 | visited := map[reflect.Type]bool{} 78 | 79 | // Fields found. 80 | var fields []field 81 | 82 | for len(next) > 0 { 83 | current, next = next, current[:0] 84 | count, nextCount = nextCount, map[reflect.Type]int{} 85 | 86 | for _, f := range current { 87 | if visited[f.typ] { 88 | continue 89 | } 90 | visited[f.typ] = true 91 | 92 | // Scan f.typ for fields to include. 93 | for i := 0; i < f.typ.NumField(); i++ { 94 | sf := f.typ.Field(i) 95 | if sf.PkgPath != "" { // unexported 96 | continue 97 | } 98 | name, _ := getOptions(sf.Tag.Get("toml")) 99 | if name == "-" { 100 | continue 101 | } 102 | index := make([]int, len(f.index)+1) 103 | copy(index, f.index) 104 | index[len(f.index)] = i 105 | 106 | ft := sf.Type 107 | if ft.Name() == "" && ft.Kind() == reflect.Ptr { 108 | // Follow pointer. 109 | ft = ft.Elem() 110 | } 111 | 112 | // Record found field and index sequence. 113 | if name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct { 114 | tagged := name != "" 115 | if name == "" { 116 | name = sf.Name 117 | } 118 | fields = append(fields, field{name, tagged, index, ft}) 119 | if count[f.typ] > 1 { 120 | // If there were multiple instances, add a second, 121 | // so that the annihilation code will see a duplicate. 122 | // It only cares about the distinction between 1 or 2, 123 | // so don't bother generating any more copies. 124 | fields = append(fields, fields[len(fields)-1]) 125 | } 126 | continue 127 | } 128 | 129 | // Record new anonymous struct to explore in next round. 130 | nextCount[ft]++ 131 | if nextCount[ft] == 1 { 132 | f := field{name: ft.Name(), index: index, typ: ft} 133 | next = append(next, f) 134 | } 135 | } 136 | } 137 | } 138 | 139 | sort.Sort(byName(fields)) 140 | 141 | // Delete all fields that are hidden by the Go rules for embedded fields, 142 | // except that fields with TOML tags are promoted. 143 | 144 | // The fields are sorted in primary order of name, secondary order 145 | // of field index length. Loop over names; for each name, delete 146 | // hidden fields by choosing the one dominant field that survives. 147 | out := fields[:0] 148 | for advance, i := 0, 0; i < len(fields); i += advance { 149 | // One iteration per name. 150 | // Find the sequence of fields with the name of this first field. 151 | fi := fields[i] 152 | name := fi.name 153 | for advance = 1; i+advance < len(fields); advance++ { 154 | fj := fields[i+advance] 155 | if fj.name != name { 156 | break 157 | } 158 | } 159 | if advance == 1 { // Only one field with this name 160 | out = append(out, fi) 161 | continue 162 | } 163 | dominant, ok := dominantField(fields[i : i+advance]) 164 | if ok { 165 | out = append(out, dominant) 166 | } 167 | } 168 | 169 | fields = out 170 | sort.Sort(byIndex(fields)) 171 | 172 | return fields 173 | } 174 | 175 | // dominantField looks through the fields, all of which are known to 176 | // have the same name, to find the single field that dominates the 177 | // others using Go's embedding rules, modified by the presence of 178 | // TOML tags. If there are multiple top-level fields, the boolean 179 | // will be false: This condition is an error in Go and we skip all 180 | // the fields. 181 | func dominantField(fields []field) (field, bool) { 182 | // The fields are sorted in increasing index-length order. The winner 183 | // must therefore be one with the shortest index length. Drop all 184 | // longer entries, which is easy: just truncate the slice. 185 | length := len(fields[0].index) 186 | tagged := -1 // Index of first tagged field. 187 | for i, f := range fields { 188 | if len(f.index) > length { 189 | fields = fields[:i] 190 | break 191 | } 192 | if f.tag { 193 | if tagged >= 0 { 194 | // Multiple tagged fields at the same level: conflict. 195 | // Return no field. 196 | return field{}, false 197 | } 198 | tagged = i 199 | } 200 | } 201 | if tagged >= 0 { 202 | return fields[tagged], true 203 | } 204 | // All remaining fields have the same length. If there's more than one, 205 | // we have a conflict (two fields named "X" at the same level) and we 206 | // return no field. 207 | if len(fields) > 1 { 208 | return field{}, false 209 | } 210 | return fields[0], true 211 | } 212 | 213 | var fieldCache struct { 214 | sync.RWMutex 215 | m map[reflect.Type][]field 216 | } 217 | 218 | // cachedTypeFields is like typeFields but uses a cache to avoid repeated work. 219 | func cachedTypeFields(t reflect.Type) []field { 220 | fieldCache.RLock() 221 | f := fieldCache.m[t] 222 | fieldCache.RUnlock() 223 | if f != nil { 224 | return f 225 | } 226 | 227 | // Compute fields without lock. 228 | // Might duplicate effort but won't hold other computations back. 229 | f = typeFields(t) 230 | if f == nil { 231 | f = []field{} 232 | } 233 | 234 | fieldCache.Lock() 235 | if fieldCache.m == nil { 236 | fieldCache.m = map[reflect.Type][]field{} 237 | } 238 | fieldCache.m[t] = f 239 | fieldCache.Unlock() 240 | return f 241 | } 242 | -------------------------------------------------------------------------------- /_third_party/github.com/bradfitz/slice/COPYING: -------------------------------------------------------------------------------- 1 | This package is licensed under the same terms as Go itself and 2 | has the same contribution / CLA requirements. 3 | 4 | -------------------------------------------------------------------------------- /_third_party/github.com/bradfitz/slice/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /_third_party/github.com/bradfitz/slice/README: -------------------------------------------------------------------------------- 1 | See http://godoc.org/github.com/bradfitz/slice 2 | -------------------------------------------------------------------------------- /_third_party/github.com/bradfitz/slice/slice.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package slice provides a slice sorting function. 6 | // 7 | // It uses gross, low-level operations to make it easy to sort 8 | // arbitrary slices with only a less function, without defining a new 9 | // type with Len and Swap operations. 10 | package slice 11 | 12 | import ( 13 | "fmt" 14 | "reflect" 15 | "sort" 16 | "unsafe" 17 | ) 18 | 19 | const useReflectSwap = false 20 | 21 | const ptrSize = unsafe.Sizeof((*int)(nil)) 22 | 23 | // Sort sorts the provided slice using the function less. 24 | // If slice is not a slice, Sort panics. 25 | func Sort(slice interface{}, less func(i, j int) bool) { 26 | sort.Sort(SortInterface(slice, less)) 27 | } 28 | 29 | // SortInterface returns a sort.Interface to sort the provided slice 30 | // using the function less. 31 | func SortInterface(slice interface{}, less func(i, j int) bool) sort.Interface { 32 | sv := reflect.ValueOf(slice) 33 | if sv.Kind() != reflect.Slice { 34 | panic(fmt.Sprintf("slice.Sort called with non-slice value of type %T", slice)) 35 | } 36 | 37 | size := sv.Type().Elem().Size() 38 | ss := &lenLesser{ 39 | less: less, 40 | slice: sv, 41 | size: size, 42 | len: sv.Len(), 43 | } 44 | 45 | var baseMem unsafe.Pointer 46 | if ss.len > 0 { 47 | baseMem = unsafe.Pointer(sv.Index(0).Addr().Pointer()) 48 | } 49 | 50 | switch { 51 | case useReflectSwap: 52 | return &reflectSwap{ 53 | temp: reflect.New(sv.Type().Elem()).Elem(), 54 | lenLesser: ss, 55 | } 56 | case uintptr(size) == ptrSize: 57 | return &pointerSwap{ 58 | baseMem: baseMem, 59 | lenLesser: ss, 60 | } 61 | case size == 8: 62 | return &swap8{ 63 | baseMem: baseMem, 64 | lenLesser: ss, 65 | } 66 | case size == 4: 67 | return &swap4{ 68 | baseMem: baseMem, 69 | lenLesser: ss, 70 | } 71 | default: 72 | // Make a properly-typed (for GC) chunk of memory for swap 73 | // operations. 74 | temp := reflect.New(sv.Type().Elem()).Elem() 75 | tempMem := unsafe.Pointer(temp.Addr().Pointer()) 76 | ms := newMemSwap(size, baseMem, tempMem) 77 | ms.lenLesser = ss 78 | return ms 79 | } 80 | } 81 | 82 | func newMemSwap(size uintptr, baseMem, tempMem unsafe.Pointer) *memSwap { 83 | tempSlice := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ 84 | Data: uintptr(tempMem), 85 | Len: int(size), 86 | Cap: int(size), 87 | })) 88 | ms := &memSwap{ 89 | imem: *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{Data: uintptr(baseMem), Len: int(size), Cap: int(size)})), 90 | jmem: *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{Data: uintptr(baseMem), Len: int(size), Cap: int(size)})), 91 | temp: tempSlice, 92 | size: size, 93 | base: baseMem, 94 | } 95 | ms.ibase = (*uintptr)(unsafe.Pointer(&ms.imem)) 96 | ms.jbase = (*uintptr)(unsafe.Pointer(&ms.jmem)) 97 | return ms 98 | } 99 | 100 | type lenLesser struct { 101 | less func(i, j int) bool 102 | slice reflect.Value 103 | len int 104 | size uintptr 105 | } 106 | 107 | func (s *lenLesser) Len() int { return s.len } 108 | 109 | func (s *lenLesser) Less(i, j int) bool { 110 | return s.less(i, j) 111 | } 112 | 113 | // reflectSwap is the pure reflect-based swap. It's compiled out by 114 | // default because it's ridiculously slow. But it's kept here in case 115 | // you want to see for yourself. 116 | type reflectSwap struct { 117 | temp reflect.Value 118 | *lenLesser 119 | } 120 | 121 | func (s *reflectSwap) Swap(i, j int) { 122 | s.temp.Set(s.slice.Index(i)) 123 | s.slice.Index(i).Set(s.slice.Index(j)) 124 | s.slice.Index(j).Set(s.temp) 125 | } 126 | 127 | // pointerSwap swaps pointers. 128 | type pointerSwap struct { 129 | baseMem unsafe.Pointer 130 | *lenLesser 131 | } 132 | 133 | func (s *pointerSwap) Swap(i, j int) { 134 | base := s.baseMem 135 | ip := (*unsafe.Pointer)(unsafe.Pointer(uintptr(base) + uintptr(i)*ptrSize)) 136 | jp := (*unsafe.Pointer)(unsafe.Pointer(uintptr(base) + uintptr(j)*ptrSize)) 137 | *ip, *jp = *jp, *ip 138 | } 139 | 140 | // swap8 swaps 8-byte non-pointer elements. 141 | type swap8 struct { 142 | baseMem unsafe.Pointer 143 | *lenLesser 144 | } 145 | 146 | func (s *swap8) Swap(i, j int) { 147 | base := s.baseMem 148 | ip := (*uint64)(unsafe.Pointer(uintptr(base) + uintptr(i)*8)) 149 | jp := (*uint64)(unsafe.Pointer(uintptr(base) + uintptr(j)*8)) 150 | *ip, *jp = *jp, *ip 151 | } 152 | 153 | // swap4 swaps 4-byte non-pointer elements. 154 | type swap4 struct { 155 | baseMem unsafe.Pointer 156 | *lenLesser 157 | } 158 | 159 | func (s *swap4) Swap(i, j int) { 160 | base := s.baseMem 161 | ip := (*uint32)(unsafe.Pointer(uintptr(base) + uintptr(i)*4)) 162 | jp := (*uint32)(unsafe.Pointer(uintptr(base) + uintptr(j)*4)) 163 | *ip, *jp = *jp, *ip 164 | } 165 | 166 | // memSwap swaps regions of memory 167 | type memSwap struct { 168 | imem []byte 169 | jmem []byte 170 | temp []byte // properly typed slice of memory to use as temp space 171 | ibase *uintptr // ibase points to the Data word of imem 172 | jbase *uintptr // jbase points to the Data word of jmem 173 | size uintptr 174 | base unsafe.Pointer 175 | *lenLesser 176 | } 177 | 178 | func (s *memSwap) Swap(i, j int) { 179 | imem, jmem, temp := s.imem, s.jmem, s.temp 180 | base, size := s.base, s.size 181 | *(*uintptr)(unsafe.Pointer(&imem)) = uintptr(base) + size*uintptr(i) 182 | *(*uintptr)(unsafe.Pointer(&jmem)) = uintptr(base) + size*uintptr(j) 183 | copy(temp, imem) 184 | copy(imem, jmem) 185 | copy(jmem, temp) 186 | } 187 | -------------------------------------------------------------------------------- /_third_party/github.com/bradfitz/slice/slice_test.go: -------------------------------------------------------------------------------- 1 | package slice 2 | 3 | import ( 4 | "math/rand" 5 | "reflect" 6 | "sort" 7 | "testing" 8 | "unsafe" 9 | ) 10 | 11 | const nItem = 50000 12 | 13 | type S struct { 14 | s string 15 | i int64 16 | p *int 17 | b byte 18 | } 19 | 20 | type lessCall struct { 21 | i, j int 22 | res bool 23 | } 24 | 25 | type lessLogger struct { 26 | sort.Interface 27 | dst *[]lessCall 28 | } 29 | 30 | func (l lessLogger) Less(i, j int) bool { 31 | got := l.Interface.Less(i, j) 32 | *l.dst = append(*l.dst, lessCall{i, j, got}) 33 | return got 34 | } 35 | 36 | func TestSwapMem(t *testing.T) { 37 | buf := []byte{ 38 | 1, 2, 3, 4, 39 | 5, 6, 7, 8, 40 | 0, 0, 0, 0, 41 | } 42 | const size = 4 43 | ms := newMemSwap(size, unsafe.Pointer(&buf[0]), unsafe.Pointer(&buf[8])) 44 | ms.Swap(0, 1) 45 | want := []byte{ 46 | 5, 6, 7, 8, 47 | 1, 2, 3, 4, 48 | 1, 2, 3, 4, 49 | } 50 | if !reflect.DeepEqual(buf, want) { 51 | t.Errorf("buf = %v; want %v", buf, want) 52 | } 53 | } 54 | 55 | func TestSort3(t *testing.T) { 56 | x := []int{3, 2, 1} 57 | var sawOutside, sawInside []lessCall 58 | si := SortInterface(x, func(i, j int) bool { 59 | ret := x[i] < x[j] 60 | sawInside = append(sawInside, lessCall{i, j, ret}) 61 | return ret 62 | }) 63 | sort.Sort(lessLogger{si, &sawOutside}) 64 | want := []int{1, 2, 3} 65 | if !reflect.DeepEqual(x, want) { 66 | t.Errorf("bad Sort = %v; want %v", x, want) 67 | } 68 | if !reflect.DeepEqual(sawOutside, sawInside) { 69 | t.Errorf("assembly goo wrong. Inner func & outer interface saw different results:\nInner: %v\nOuter: %v\n", sawInside, sawOutside) 70 | } 71 | } 72 | 73 | func TestSort(t *testing.T) { 74 | rand.Seed(1) 75 | x := make([]int64, nItem) 76 | for i := range x { 77 | x[i] = int64(rand.Intn(nItem * 10)) 78 | } 79 | x2 := append([]int64(nil), x...) 80 | 81 | Sort(x, func(i, j int) bool { 82 | return x[i] < x[j] 83 | }) 84 | sort.Sort(Int64Slice(x2)) 85 | if !reflect.DeepEqual(x, x2) { 86 | t.Errorf("sorting failed") 87 | } 88 | } 89 | 90 | func TestSortStruct(t *testing.T) { 91 | rand.Seed(1) 92 | x := make([]S, nItem) 93 | for i := range x { 94 | x[i] = S{i: int64(rand.Intn(nItem * 10))} 95 | } 96 | x2 := append([]S(nil), x...) 97 | 98 | Sort(x, func(i, j int) bool { 99 | return x[i].i < x[j].i 100 | }) 101 | sort.Sort(SSlice(x2)) 102 | if !reflect.DeepEqual(x, x2) { 103 | t.Errorf("sorting failed") 104 | } 105 | } 106 | 107 | func BenchmarkSortInt64New(b *testing.B) { 108 | rand.Seed(1) 109 | x := make([]int64, nItem) 110 | for i := 0; i < b.N; i++ { 111 | for j := range x { 112 | x[j] = int64(rand.Intn(nItem * 10)) 113 | } 114 | Sort(x, func(i, j int) bool { 115 | return x[i] < x[j] 116 | }) 117 | } 118 | } 119 | 120 | func BenchmarkSortInt64Old(b *testing.B) { 121 | rand.Seed(1) 122 | x := make([]int64, nItem) 123 | for i := 0; i < b.N; i++ { 124 | for j := range x { 125 | x[j] = int64(rand.Intn(nItem * 10)) 126 | } 127 | sort.Sort(Int64Slice(x)) 128 | } 129 | } 130 | 131 | func BenchmarkSortInt32New(b *testing.B) { 132 | rand.Seed(1) 133 | x := make([]int32, nItem) 134 | for i := 0; i < b.N; i++ { 135 | for j := range x { 136 | x[j] = int32(rand.Intn(nItem * 10)) 137 | } 138 | Sort(x, func(i, j int) bool { 139 | return x[i] < x[j] 140 | }) 141 | } 142 | } 143 | 144 | func BenchmarkSortInt32Old(b *testing.B) { 145 | rand.Seed(1) 146 | x := make([]int32, nItem) 147 | for i := 0; i < b.N; i++ { 148 | for j := range x { 149 | x[j] = int32(rand.Intn(nItem * 10)) 150 | } 151 | sort.Sort(Int32Slice(x)) 152 | } 153 | } 154 | 155 | func BenchmarkSortStructOld(b *testing.B) { 156 | rand.Seed(1) 157 | x := make([]S, nItem) 158 | for i := 0; i < b.N; i++ { 159 | for j := range x { 160 | x[j] = S{i: int64(rand.Intn(nItem * 10))} 161 | } 162 | sort.Sort(SSlice(x)) 163 | } 164 | } 165 | 166 | func BenchmarkSortStructNew(b *testing.B) { 167 | rand.Seed(1) 168 | x := make([]S, nItem) 169 | for i := 0; i < b.N; i++ { 170 | for j := range x { 171 | x[j] = S{i: int64(rand.Intn(nItem * 10))} 172 | } 173 | Sort(x, func(i, j int) bool { 174 | return x[i].i < x[j].i 175 | }) 176 | } 177 | } 178 | 179 | // Int64Slice attaches the methods of sort.Interface to []int64, sorting in increasing order. 180 | type Int64Slice []int64 181 | 182 | func (s Int64Slice) Len() int { return len(s) } 183 | func (s Int64Slice) Less(i, j int) bool { return s[i] < s[j] } 184 | func (s Int64Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 185 | 186 | // Int32Slice attaches the methods of sort.Interface to []int32, sorting in increasing order. 187 | type Int32Slice []int32 188 | 189 | func (s Int32Slice) Len() int { return len(s) } 190 | func (s Int32Slice) Less(i, j int) bool { return s[i] < s[j] } 191 | func (s Int32Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 192 | 193 | type SSlice []S 194 | 195 | func (s SSlice) Len() int { return len(s) } 196 | func (s SSlice) Less(i, j int) bool { return s[i].i < s[j].i } 197 | func (s SSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 198 | -------------------------------------------------------------------------------- /cmd/httpunit/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | httpunit tests compliance of web and net servers with desired output. 4 | 5 | It has three modes. All modes support flag options. The toml and hiera modes 6 | may be used together. 7 | 8 | If toml is specified, that toml file is read into the configuration. See 9 | the TOML section below for format. 10 | 11 | If hiera is specified, the listeners from it are extracted and tested. 12 | 13 | If url is specified, it checks only the specified URL with optional IP 14 | address, status code, and regex. If url does not contain a scheme ("https://", 15 | "http://"), "http://" is prefixed. The IP may be an empty string to indicate 16 | all IP addresses resolved to from the URL's hostname. 17 | 18 | Usage: 19 | httpunit [flag] [-hiera="path/to/sets.json"] [-toml="/path/to/httpunit.toml"] [url] [ip] [code] [regex] 20 | 21 | The flags are: 22 | -filter="" 23 | if specified, only uses this IP address; may end with "." to 24 | filter by IPs that start with the filter 25 | -no10=false 26 | no RFC1918 addresses 27 | -timeout="3s" 28 | connection timeout 29 | -tags="" 30 | if specified, only runs plans that are tagged with one of the 31 | tags specified. You can specify more than one tag, seperated by commas 32 | -protos="" 33 | if specified, only runs plans where the URL contains the given 34 | protocol. Valid protocols are: http,https,tcp,tcp4,tcp6,udp,udp4,udp6,ip,ip4,ip6 35 | You can specify more than one protocol, seperated by commas 36 | -header="X-Request-Guid" 37 | in more verbose mode, print this HTTP header 38 | -v 39 | verbose output:show successes 40 | -vv 41 | more verbose output: show -header, cert details 42 | 43 | URLs 44 | 45 | URLs may be specified with various protocols: http, https, tcp, 46 | udp, ip. "4" or "6" may be appended to tcp, udp, and ip (as per 47 | http://golang.org/pkg/net/#Dial). tcp and udp must specify a port, or default 48 | to 0. http and https may specify a port to override the default. 49 | 50 | TOML 51 | 52 | The toml file (https://github.com/toml-lang/toml) has two sections: IPs, 53 | a table of search and replace regexes, and Plan, an array of tables listing 54 | test plans. 55 | 56 | The IPs table has keys as regexes and values as lists of replacements. A 57 | "*" specifies to perform DNS resolution. If an address contains something 58 | of the form "(x+y)", it is replaced with the sum of x and y. 59 | 60 | The plan table array can specify the label, url and ips (array of 61 | string). text, code (number), and regex may be specified for http[s] URLs 62 | to require matching returns. code defaults to 200. 63 | 64 | An example file: 65 | 66 | [IPs] 67 | BASEIP = ["87.65.43."] 68 | '^(\d+)$' = ["*", "BASEIP$1", "BASEIP($1+64)", "1.2.3.$1"] 69 | '^(\d+)INT$' = ["*", "10.0.1.$1", "10.0.2.$1", "BASEIP$1", "BASEIP($1+64)"] 70 | 71 | [[plan]] 72 | label = "api" 73 | url = "http://api.example.com/" 74 | ips = ["16", "8.7.6.5"] 75 | text = "API for example.com" 76 | regex = "some regex" 77 | 78 | [[plan]] 79 | label = "redirect" 80 | url = "https://example.com/redirect" 81 | ips = ["*", "20INT"] 82 | code = 301 83 | 84 | [[plan]] 85 | label = "mail" 86 | url = "tcp://mail-host.com:25" 87 | 88 | [[plan]] 89 | label = "self-signed" 90 | url = "https://internal.example.com" 91 | insecureSkipVerify = true 92 | code = 200 93 | */ 94 | package main 95 | -------------------------------------------------------------------------------- /cmd/httpunit/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "crypto/sha1" 7 | "crypto/x509" 8 | "crypto/x509/pkix" 9 | "encoding/json" 10 | "flag" 11 | "fmt" 12 | "log" 13 | "os" 14 | "regexp" 15 | "strconv" 16 | "strings" 17 | "time" 18 | 19 | "github.com/StackExchange/httpunit" 20 | "github.com/StackExchange/httpunit/_third_party/github.com/BurntSushi/toml" 21 | "github.com/StackExchange/httpunit/_third_party/github.com/bradfitz/slice" 22 | ) 23 | 24 | var ( 25 | filter = flag.String("filter", "", `if specified, only uses this IP address; may end with "." to filter by IPs that start with the filter`) 26 | no10 = flag.Bool("no10", false, "no RFC1918 addresses") 27 | hiera = flag.String("hiera", "", "tests listeners members from the specified hieradata/sets.json file") 28 | tomlLoc = flag.String("toml", "", "httpunit.toml location") 29 | verbose1 = flag.Bool("v", false, "verbose output: show successes") 30 | verbose2 = flag.Bool("vv", false, "more verbose output: show -header, cert details") 31 | header = flag.String("header", "X-Request-Guid", "an HTTP to header to print in verbose mode") 32 | timeout = flag.Duration("timeout", httpunit.Timeout, "connection timeout") 33 | ipMap = flag.String("ipmap", "", `override or set one entry if the IPs table, in "key=value" format, where value is a JSON array of strings; for example: -ipmap='BASEIP=["10.2.3.", "1.4.5."]'`) 34 | tags = flag.String("tags", "", "if specified, only run plans that are tagged with these tags. Use comma seperated to include mutiple tags, e.g. \"normal,extended\"") 35 | protos = flag.String("protos", "", "if specified, only run tests that use the designated protocol. Valid choices are: http,https,tcp,tcp4,tcp6,udp,udp4,udp6,ip,ip4,ip6. e.g. \"http,https\"") 36 | ) 37 | 38 | func printNames(x []pkix.AttributeTypeAndValue) []string { 39 | var r []string 40 | for _, v := range x { 41 | r = append(r, v.Value.(string)) 42 | } 43 | return r 44 | } 45 | 46 | func printStringsIfExist(w *bufio.Writer, label string, list []string) { 47 | switch { 48 | case len(list) == 1: 49 | fmt.Fprintf(w, `%s: %q`, label, list[0]) 50 | case len(list) > 1: 51 | fmt.Fprintf(w, `%s: %q`, label, list) 52 | //fmt.Fprintf(w, `%s: "{%s"}`, label, strings.Join(list, `" "`)) 53 | default: 54 | // do nothing. 55 | } 56 | } 57 | 58 | func printCert(w *bufio.Writer, c *x509.Certificate) { 59 | h := "\n\t\t" 60 | fmt.Fprintf(w, h+"expires: %v", c.NotAfter) 61 | fmt.Fprintf(w, h+"serial: %x", c.SerialNumber) 62 | fmt.Fprintf(w, h+"fingerprint: %x", sha1.Sum(c.Raw)) 63 | fmt.Fprintf(w, h+"version: %d", c.Version) 64 | fmt.Fprintf(w, h+"domains: %q", c.DNSNames) 65 | if len(c.EmailAddresses) > 0 { 66 | fmt.Fprintf(w, h+"emails: %q", c.EmailAddresses) 67 | } 68 | if len(c.IPAddresses) > 0 { 69 | fmt.Fprintf(w, h+"IPs: %q", c.IPAddresses) 70 | } 71 | printStringsIfExist(w, h+"Names", printNames(c.Issuer.Names)) 72 | printStringsIfExist(w, h+"ExtraNames", printNames(c.Issuer.ExtraNames)) 73 | printStringsIfExist(w, h+"Country", c.Issuer.Country) 74 | printStringsIfExist(w, h+"Organization", c.Issuer.Organization) 75 | printStringsIfExist(w, h+"OrganizationalUnit", c.Issuer.OrganizationalUnit) 76 | printStringsIfExist(w, h+"Locality", c.Issuer.Locality) 77 | printStringsIfExist(w, h+"Province", c.Issuer.Province) 78 | 79 | } 80 | 81 | func doMain() int { 82 | var seen_error int 83 | flag.Parse() 84 | if *verbose2 { 85 | *verbose1 = true 86 | } 87 | plans := &httpunit.Plans{ 88 | IPs: make(httpunit.IPMap), 89 | } 90 | var err error 91 | if *timeout > 0 { 92 | httpunit.Timeout = *timeout 93 | } 94 | if *tomlLoc != "" { 95 | if _, err := toml.DecodeFile(*tomlLoc, &plans); err != nil { 96 | log.Fatal(err) 97 | } 98 | } 99 | if *hiera != "" { 100 | p, err := httpunit.ExtractHiera(*hiera) 101 | plans.Plans = append(plans.Plans, p...) 102 | if err != nil { 103 | log.Fatal(err) 104 | } 105 | } 106 | if args := flag.Args(); len(args) > 0 { 107 | if plans.Plans != nil { 108 | log.Fatal("cannot manually specify URLs with other modes") 109 | } 110 | u := args[0] 111 | args = args[1:] 112 | tp := httpunit.TestPlan{ 113 | Label: u, 114 | } 115 | if !strings.Contains(u, "://") { 116 | u = "http://" + u 117 | } 118 | tp.URL = u 119 | if len(args) > 0 { 120 | if args[0] != "" { 121 | tp.IPs = []string{args[0]} 122 | } 123 | args = args[1:] 124 | } 125 | if len(args) > 0 { 126 | code, err := strconv.Atoi(args[0]) 127 | if err != nil { 128 | log.Fatalf("bad status code: %v: %v", args[0], err) 129 | } 130 | args = args[1:] 131 | tp.Code = code 132 | } 133 | if len(args) > 0 { 134 | _, err := regexp.Compile(args[0]) 135 | if err != nil { 136 | log.Fatalf("bad regex: %v: %v", args[0], err) 137 | } 138 | tp.Regex = args[0] 139 | args = args[1:] 140 | } 141 | if len(args) > 0 { 142 | log.Fatalf("too many arguments") 143 | } 144 | plans.Plans = []*httpunit.TestPlan{&tp} 145 | } 146 | if *ipMap != "" { 147 | sp := strings.SplitN(*ipMap, "=", 2) 148 | if len(sp) != 2 { 149 | log.Fatalf("expected key=value in -ipmap") 150 | } 151 | var vals []string 152 | if err := json.Unmarshal([]byte(sp[1]), &vals); err != nil { 153 | log.Fatalf("ipmap: %v", err) 154 | } 155 | plans.IPs[sp[0]] = vals 156 | } 157 | if len(plans.Plans) == 0 { 158 | log.Fatalf("no tests specified") 159 | } 160 | var tagFilter []string 161 | if *tags != "" { 162 | tagFilter = strings.Split(*tags, ",") 163 | } 164 | var protoFilter []string 165 | if *protos != "" { 166 | protoFilter = strings.Split(*protos, ",") 167 | } 168 | rch, count, err := plans.Test(*filter, *no10, tagFilter, protoFilter) 169 | if err != nil { 170 | log.Fatal(err) 171 | } 172 | var res httpunit.Results 173 | delay := time.Second / 2 174 | next := time.After(delay) 175 | got := 0 176 | start := time.Now() 177 | 178 | Loop: 179 | for { 180 | select { 181 | case <-next: 182 | fmt.Fprintf(os.Stderr, "%v of %v done in %v\n", got, count, time.Since(start)) 183 | next = time.After(delay) 184 | case r, ok := <-rch: 185 | if !ok { 186 | fmt.Fprintf(os.Stderr, "%v of %v done in %v\n", got, count, time.Since(start)) 187 | break Loop 188 | } 189 | res = append(res, r) 190 | got++ 191 | } 192 | } 193 | 194 | pidx := make(map[*httpunit.TestPlan]int) 195 | for i, p := range plans.Plans { 196 | pidx[p] = i 197 | } 198 | slice.Sort(res, func(i, j int) bool { 199 | a := res[i].Plan 200 | b := res[j].Plan 201 | pa := pidx[a] 202 | pb := pidx[b] 203 | if pa != pb { 204 | return pa < pb 205 | } 206 | ai := res[i].Case.IP 207 | bi := res[j].Case.IP 208 | if len(ai) != len(bi) { 209 | return len(ai) < len(bi) 210 | } 211 | for k, v := range ai { 212 | if bi[k] != v { 213 | return ai[k] < bi[k] 214 | } 215 | } 216 | return false 217 | }) 218 | for _, r := range res { 219 | fromDNS := "IP" 220 | if r.Case.FromDNS { 221 | fromDNS = "DNS" 222 | } 223 | ip := fmt.Sprintf("%v=%v", fromDNS, r.Case.IP) 224 | var status string 225 | var verbose bytes.Buffer 226 | ver := bufio.NewWriter(&verbose) 227 | if r.Case.ExpectCode > 0 { 228 | status += fmt.Sprint(r.Case.ExpectCode) 229 | } 230 | if r.Case.ExpectText != "" { 231 | status += " T" 232 | } 233 | if r.Case.ExpectRegex != nil { 234 | status += " R" 235 | } 236 | if status != "" { 237 | status = " (" + status + ")" 238 | } 239 | if !*verbose1 && r.Result.Result == nil { 240 | continue 241 | } 242 | if *verbose2 { 243 | if resp := r.Result.Resp; resp != nil { 244 | if *header != "" { 245 | if h := resp.Header.Get(*header); h != "" { 246 | fmt.Fprintf(ver, "\n\theader %s: %s", *header, h) 247 | } 248 | } 249 | if t := resp.TLS; t != nil { 250 | for i, c := range t.PeerCertificates { 251 | fmt.Fprintf(ver, "\n\tcert %v:", i) 252 | printCert(ver, c) 253 | 254 | } 255 | for j, v := range t.VerifiedChains { 256 | for i, c := range v { 257 | fmt.Fprintf(ver, "\n\tVerified chain %d.%d:", j, i) 258 | printCert(ver, c) 259 | } 260 | } 261 | } 262 | } 263 | 264 | } 265 | ver.Flush() 266 | fmt.Printf("==== %v: %v %s%s%s\n", r.Plan.Label, r.Plan.URL, ip, status, verbose.String()) 267 | if r.Result.Result != nil { 268 | fmt.Println("ERROR:", r.Result.Result) 269 | seen_error = 1 270 | } 271 | } 272 | return seen_error 273 | } 274 | 275 | func main() { 276 | os.Exit(doMain()) // http://stackoverflow.com/a/27629493/71978 277 | } 278 | -------------------------------------------------------------------------------- /httpunit.go: -------------------------------------------------------------------------------- 1 | // Package httpunit tests compliance of net services. 2 | package httpunit 3 | 4 | import ( 5 | "crypto/tls" 6 | "encoding/json" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "net" 11 | "net/http" 12 | "net/url" 13 | "regexp" 14 | "sort" 15 | "strconv" 16 | "strings" 17 | "sync" 18 | "time" 19 | ) 20 | 21 | type Plans struct { 22 | Plans []*TestPlan `toml:"plan"` 23 | IPs IPMap 24 | } 25 | 26 | /* 27 | ExtractHiera returns listener members from a hiera sets.json file. For 28 | example, the following file would create three entries for the given IPs 29 | and ports. Other items present in the file are ignored. 30 | 31 | { 32 | "iptables::sets::sets": { 33 | "listeners": { 34 | "members": { 35 | "10.0.1.2,tcp:80": { 36 | "comment": "first load balancer" 37 | }, 38 | "10.0.3.4,tcp:80": { 39 | "comment": "second load balancer" 40 | }, 41 | "10.0.7.8,tcp:25": { 42 | "comment": "mail servers" 43 | } 44 | } 45 | } 46 | } 47 | } 48 | 49 | */ 50 | func ExtractHiera(fname string) ([]*TestPlan, error) { 51 | b, err := ioutil.ReadFile(fname) 52 | if err != nil { 53 | return nil, err 54 | } 55 | var hs hieraSets 56 | if err := json.Unmarshal(b, &hs); err != nil { 57 | return nil, err 58 | } 59 | var plans []*TestPlan 60 | for addr := range hs.Iptables.Listeners.Members { 61 | sp := strings.Split(addr, ":") 62 | if len(sp) != 2 { 63 | return nil, fmt.Errorf("unrecognized hiera address: %s", addr) 64 | } 65 | port, err := strconv.Atoi(sp[1]) 66 | if err != nil { 67 | return nil, fmt.Errorf("bad hiera port %s in %s", sp[1], addr) 68 | } 69 | sp = strings.Split(sp[0], ",") 70 | if len(sp) != 2 { 71 | return nil, fmt.Errorf("unrecognized hiera address: %s", addr) 72 | } 73 | ip, scheme := sp[0], sp[1] 74 | plans = append(plans, &TestPlan{ 75 | Label: addr, 76 | URL: fmt.Sprintf("%s://%s:%d", scheme, ip, port), 77 | }) 78 | } 79 | return plans, nil 80 | } 81 | 82 | type hieraSets struct { 83 | Iptables struct { 84 | Listeners struct { 85 | Config string `json:"config"` 86 | Members map[string]struct { 87 | Comment string `json:"comment"` 88 | } `json:"members"` 89 | } `json:"listeners"` 90 | } `json:"iptables::sets::sets"` 91 | } 92 | 93 | type PlanResult struct { 94 | Plan *TestPlan 95 | Case *TestCase 96 | Result *TestResult 97 | } 98 | 99 | type Results []*PlanResult 100 | 101 | // Test performs tests. Filter optionally specifies an IP filter to use. no10 102 | // disallows 10.* addresses. It returns a channel where results will be sent 103 | // when complete, and the total number of results to expect. The channel is 104 | // closed once all results are completed. 105 | func (ps *Plans) Test(filter string, no10 bool, tagFilters []string, protoFilters []string) (<-chan *PlanResult, int, error) { 106 | var wg sync.WaitGroup 107 | count := 0 108 | ch := make(chan *PlanResult) 109 | labels := make(map[string]bool) 110 | for _, p := range ps.Plans { 111 | var matchedTag bool 112 | hasTagFilter := len(tagFilters) > 0 113 | if hasTagFilter { 114 | if len(p.Tags) == 0 { 115 | continue 116 | } 117 | for _, thisTag := range tagFilters { 118 | for _, planTag := range p.Tags { 119 | if planTag == thisTag { 120 | matchedTag = true 121 | continue 122 | } 123 | } 124 | } 125 | } 126 | if hasTagFilter && !matchedTag { 127 | continue 128 | } 129 | if labels[p.Label] { 130 | return nil, 0, fmt.Errorf("duplicate label: %v", p.Label) 131 | } 132 | labels[p.Label] = true 133 | cs, err := p.Cases(filter, no10, ps.IPs, protoFilters) 134 | if err != nil { 135 | return nil, 0, fmt.Errorf("%v: %v", p.Label, err) 136 | } 137 | for _, c := range cs { 138 | wg.Add(1) 139 | count++ 140 | go func(p *TestPlan, c *TestCase) { 141 | r := c.Test() 142 | ch <- &PlanResult{ 143 | Plan: p, 144 | Case: c, 145 | Result: r, 146 | } 147 | wg.Done() 148 | }(p, c) 149 | } 150 | } 151 | go func() { 152 | wg.Wait() 153 | close(ch) 154 | }() 155 | return ch, count, nil 156 | } 157 | 158 | // IPMap is a map of regular expressions to replacements. 159 | type IPMap map[string][]string 160 | 161 | var reAdd = regexp.MustCompile(`\((\d+)\+(\d+)\)`) 162 | 163 | // Expand expands s into IP addresses. A successful return will consist of 164 | // valid IP addresses or "*". The following process is repeated until there 165 | // are no changes. If an address is a valid IP address or "*", no further 166 | // changes to it are done. If it contains the form "(x+y)", x and y are added 167 | // and replaced. Otherwise a regular expression search and replace is done 168 | // for each key of i with its values. 169 | func (i IPMap) Expand(s string) ([]string, error) { 170 | ir := make(map[*regexp.Regexp][]string) 171 | for k, v := range i { 172 | r, err := regexp.Compile(k) 173 | if err != nil { 174 | return nil, fmt.Errorf("%v: %v", k, err) 175 | } 176 | ir[r] = v 177 | } 178 | addrs := []string{s} 179 | for { 180 | if len(addrs) > 2000 { 181 | return nil, fmt.Errorf("address limit reached: maybe you need ^ and $ around a regex?") 182 | } 183 | var next []string 184 | for _, a := range addrs { 185 | if a == "*" { 186 | next = append(next, a) 187 | continue 188 | } 189 | if ip := net.ParseIP(a); ip != nil { 190 | next = append(next, ip.String()) 191 | continue 192 | } 193 | if reAdd.MatchString(a) { 194 | n := reAdd.ReplaceAllStringFunc(a, func(s string) string { 195 | m := reAdd.FindStringSubmatch(s) 196 | l, _ := strconv.Atoi(m[1]) 197 | r, _ := strconv.Atoi(m[2]) 198 | return strconv.Itoa(l + r) 199 | }) 200 | if n != a { 201 | next = append(next, n) 202 | continue 203 | } 204 | } 205 | added := false 206 | for r, reps := range ir { 207 | for _, rep := range reps { 208 | b := r.ReplaceAllString(a, rep) 209 | if a != b { 210 | next = append(next, b) 211 | added = true 212 | } 213 | } 214 | if added { 215 | break 216 | } 217 | } 218 | if !added { 219 | return nil, fmt.Errorf("unused address: %v", a) 220 | } 221 | } 222 | match := strEqual(addrs, next) 223 | addrs = next 224 | if match { 225 | break 226 | } 227 | } 228 | for _, a := range addrs { 229 | if ip := net.ParseIP(a); ip == nil && a != "*" { 230 | return nil, fmt.Errorf("bad ip: %v", a) 231 | } 232 | } 233 | return addrs, nil 234 | } 235 | 236 | func strEqual(a, b []string) bool { 237 | sort.Strings(a) 238 | sort.Strings(b) 239 | if len(a) != len(b) { 240 | return false 241 | } 242 | for k, v := range a { 243 | if b[k] != v { 244 | return false 245 | } 246 | } 247 | return true 248 | } 249 | 250 | // Timeout is the connection timeout. 251 | var Timeout = time.Second * 10 252 | 253 | // TestPlan describes a test and its permutations (IP addresses). 254 | type TestPlan struct { 255 | Label string 256 | URL string 257 | IPs []string 258 | Tags []string 259 | 260 | Code int 261 | Text string 262 | Regex string 263 | InsecureSkipVerify bool 264 | Timeout time.Duration 265 | } 266 | 267 | // Cases computes the actual test cases from a test plan. filter and no10 are described in Plans.Test. 268 | func (p *TestPlan) Cases(filter string, no10 bool, IPs IPMap, protoFilters []string) ([]*TestCase, error) { 269 | if p.Label == "" { 270 | return nil, fmt.Errorf("%v: label must not be empty", p.URL) 271 | } 272 | u, err := url.Parse(p.URL) 273 | if err != nil { 274 | return nil, err 275 | } else if u.Host == "" { 276 | return nil, fmt.Errorf("no host") 277 | } 278 | sp := strings.Split(u.Host, ":") 279 | if len(sp) > 2 { 280 | return nil, fmt.Errorf("bad host") 281 | } 282 | matchedProto := false 283 | hasProtoFilter := len(protoFilters) > 0 284 | if hasProtoFilter { 285 | for _, pf := range protoFilters { 286 | if strings.ToLower(u.Scheme) == strings.ToLower(pf) { 287 | matchedProto = true 288 | } 289 | } 290 | } 291 | if hasProtoFilter && !matchedProto { 292 | return nil, nil 293 | } 294 | host := sp[0] 295 | var port string 296 | if len(sp) > 1 { 297 | port = sp[1] 298 | } 299 | code := p.Code 300 | switch u.Scheme { 301 | case "http", "https": 302 | if code == 0 { 303 | code = 200 304 | } 305 | if port == "" { 306 | switch u.Scheme { 307 | case "http": 308 | port = "80" 309 | case "https": 310 | port = "443" 311 | } 312 | } 313 | case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6", "ip", "ip4", "ip6": 314 | if u.Path != "" || u.RawQuery != "" { 315 | return nil, fmt.Errorf("path and query must be unspecified in %s", u.Scheme) 316 | } 317 | if len(p.IPs) > 0 { 318 | return nil, fmt.Errorf("IPs not allowed for %s", u.Scheme) 319 | } 320 | if p.Code != 0 || p.Text != "" || p.Regex != "" { 321 | return nil, fmt.Errorf("'Expected' specs not allowed for %s", u.Scheme) 322 | } 323 | default: 324 | log.Fatalf("unknown protocol %s in %s", u.Scheme, p.URL) 325 | } 326 | var cases []*TestCase 327 | var re *regexp.Regexp 328 | if p.Regex != "" { 329 | r, err := regexp.Compile(p.Regex) 330 | if err != nil { 331 | return nil, err 332 | } 333 | re = r 334 | } 335 | add := func(fromDNS bool, ips ...string) error { 336 | for _, ip := range ips { 337 | if no10 && strings.HasPrefix(ip, "10.") { 338 | continue 339 | } 340 | if filter != "" { 341 | if strings.HasSuffix(filter, ".") { 342 | if !strings.HasPrefix(ip, filter) { 343 | continue 344 | } 345 | } else if ip != filter { 346 | continue 347 | } 348 | } 349 | 350 | c := &TestCase{ 351 | URL: u, 352 | IP: net.ParseIP(ip), 353 | Port: port, 354 | Plan: p, 355 | FromDNS: fromDNS, 356 | ExpectCode: code, 357 | ExpectText: p.Text, 358 | ExpectRegex: re, 359 | Timeout: p.Timeout * time.Second, 360 | } 361 | if c.IP == nil { 362 | return fmt.Errorf("invalid ip: %v", ip) 363 | } 364 | if c.Timeout == 0 { 365 | c.Timeout = Timeout 366 | } 367 | cases = append(cases, c) 368 | } 369 | return nil 370 | } 371 | 372 | ips := p.IPs 373 | if len(ips) == 0 { 374 | ips = []string{"*"} 375 | } 376 | 377 | for _, ip := range ips { 378 | exp, err := IPs.Expand(ip) 379 | if err != nil { 380 | return nil, err 381 | } 382 | for _, i := range exp { 383 | if i == "*" { 384 | addrs, err := net.LookupHost(host) 385 | if err != nil { 386 | cases = append(cases, &TestCase{ 387 | URL: u, 388 | Port: port, 389 | Plan: p, 390 | FromDNS: true, 391 | Error: err, 392 | }) 393 | continue 394 | } 395 | if err := add(true, addrs...); err != nil { 396 | return nil, err 397 | } 398 | } else { 399 | if err := add(false, i); err != nil { 400 | return nil, err 401 | } 402 | } 403 | } 404 | } 405 | 406 | return cases, nil 407 | } 408 | 409 | type TestCase struct { 410 | URL *url.URL 411 | IP net.IP 412 | Port string 413 | 414 | Plan *TestPlan 415 | // FromDNS is true if IP was determined with a DNS lookup. 416 | FromDNS bool 417 | // Error is populated if the TestPlan could not create a TestCase. In this case 418 | // the test is not run, and this error is passed directly to the TestResult. 419 | Error error 420 | 421 | ExpectCode int 422 | ExpectText string 423 | ExpectRegex *regexp.Regexp 424 | 425 | Timeout time.Duration 426 | } 427 | 428 | type TestResult struct { 429 | Result error 430 | Resp *http.Response 431 | 432 | Connected bool 433 | GotCode bool 434 | GotText bool 435 | GotRegex bool 436 | InvalidCert bool 437 | TimeTotal time.Duration 438 | } 439 | 440 | func (c *TestCase) addr() string { 441 | return net.JoinHostPort(c.IP.String(), c.Port) 442 | } 443 | 444 | // Test performs this test case. 445 | func (c *TestCase) Test() *TestResult { 446 | if c.Error != nil { 447 | return &TestResult{ 448 | Result: c.Error, 449 | } 450 | } 451 | switch c.URL.Scheme { 452 | case "http", "https": 453 | return c.testHTTP() 454 | default: 455 | return c.testConnect() 456 | } 457 | } 458 | 459 | func (c *TestCase) testConnect() (r *TestResult) { 460 | r = new(TestResult) 461 | t := time.Now() 462 | defer func() { 463 | r.TimeTotal = time.Now().Sub(t) 464 | }() 465 | conn, err := net.DialTimeout(c.URL.Scheme, c.addr(), c.Timeout) 466 | if err != nil { 467 | r.Result = err 468 | return 469 | } 470 | r.Connected = true 471 | conn.Close() 472 | return 473 | } 474 | 475 | func (c *TestCase) testHTTP() (r *TestResult) { 476 | r = new(TestResult) 477 | t := time.Now() 478 | defer func() { 479 | r.TimeTotal = time.Now().Sub(t) 480 | }() 481 | tr := &http.Transport{ 482 | Dial: func(network, a string) (net.Conn, error) { 483 | conn, err := net.DialTimeout(network, c.addr(), c.Timeout) 484 | if err != nil { 485 | r.Connected = false 486 | } 487 | return conn, err 488 | }, 489 | DisableKeepAlives: true, 490 | TLSClientConfig: &tls.Config{ 491 | InsecureSkipVerify: c.Plan.InsecureSkipVerify, 492 | }, 493 | } 494 | req, err := http.NewRequest("GET", c.URL.String(), nil) 495 | if err != nil { 496 | r.Result = err 497 | return 498 | } 499 | timedOut := false 500 | timout := time.AfterFunc(c.Timeout, func() { 501 | timedOut = true 502 | r.Connected = false 503 | tr.CancelRequest(req) 504 | }) 505 | defer timout.Stop() 506 | resp, err := tr.RoundTrip(req) 507 | if err != nil { 508 | if strings.HasPrefix(err.Error(), "x509") { 509 | r.InvalidCert = true 510 | } 511 | if timedOut { 512 | r.Result = fmt.Errorf("i/o timeout") 513 | } else { 514 | r.Result = err 515 | } 516 | return 517 | } 518 | defer resp.Body.Close() 519 | r.Resp = resp 520 | if resp.StatusCode != c.ExpectCode { 521 | r.Result = fmt.Errorf("unexpected status code: %d", resp.StatusCode) 522 | } else { 523 | r.GotCode = true 524 | } 525 | text, err := ioutil.ReadAll(resp.Body) 526 | if err != nil { 527 | if timedOut { 528 | r.Result = fmt.Errorf("i/o timeout") 529 | } else { 530 | r.Result = err 531 | } 532 | return 533 | } 534 | short := text 535 | if len(short) > 250 { 536 | short = short[:250] 537 | } 538 | if c.ExpectText != "" && !strings.Contains(string(text), c.ExpectText) { 539 | r.Result = fmt.Errorf("response does not contain text [%s]: %q", c.ExpectText, short) 540 | } else { 541 | r.GotText = true 542 | } 543 | if c.ExpectRegex != nil && !c.ExpectRegex.Match(text) { 544 | r.Result = fmt.Errorf("response does not match regex [%s]: %q", c.ExpectRegex, short) 545 | } else { 546 | r.GotRegex = true 547 | } 548 | return 549 | } 550 | -------------------------------------------------------------------------------- /httpunit_test.go: -------------------------------------------------------------------------------- 1 | package httpunit 2 | 3 | import "testing" 4 | 5 | func TestIPMapResolve(t *testing.T) { 6 | m := IPMap{ 7 | "BASEIP": []string{"87.65.43."}, 8 | `^(\d+)$`: []string{"*", "BASEIP$1", "BASEIP($1+64)", "123.45.67.$1"}, 9 | `^(\d+)i$`: []string{"*", "10.0.1.$1", "10.0.2.$1", "BASEIP$1", "BASEIP($1+64)", "123.45.67.$1"}, 10 | } 11 | tests := []struct { 12 | in string 13 | error bool 14 | out []string 15 | }{ 16 | { 17 | "16", 18 | false, 19 | []string{ 20 | "*", 21 | "123.45.67.16", 22 | "87.65.43.16", 23 | "87.65.43.80", 24 | }, 25 | }, 26 | { 27 | "16i", 28 | false, 29 | []string{"*", 30 | "10.0.1.16", 31 | "10.0.2.16", 32 | "123.45.67.16", 33 | "87.65.43.16", 34 | "87.65.43.80", 35 | }, 36 | }, 37 | { 38 | "unused", 39 | true, 40 | nil, 41 | }, 42 | } 43 | for i, test := range tests { 44 | ips, err := m.Expand(test.in) 45 | if err != nil && !test.error { 46 | t.Errorf("%v: expected no error, got %v", i, err) 47 | } else if err == nil && test.error { 48 | t.Errorf("%v: expected error", i) 49 | } else if !strEqual(ips, test.out) { 50 | t.Errorf("%v: expected %v, got %v", i, test.out, ips) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /mk_rpm_fpmdir.httpunit.txt: -------------------------------------------------------------------------------- 1 | exec /usr/bin/httpunit cmd/httpunit/httpunit 2 | --------------------------------------------------------------------------------