├── .travis.yml ├── LICENSE ├── README.md ├── decode.go ├── decode_test.go ├── encode.go ├── encode_test.go ├── errors.go ├── example_unmarshal_test.go ├── example_unmarshal_text_test.go ├── go.mod ├── internal └── types │ ├── array.go │ ├── table.go │ └── types.go ├── key.go ├── parse.go ├── pred.go └── tags.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.2 4 | - 1.5 5 | - 1.11 6 | - 1.12 7 | - tip 8 | 9 | script: 10 | - go test -v ./... 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Kezhu Wang 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TOML parser and encoder for Go 2 | 3 | Compatible with [TOML][] version [v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md). 4 | 5 | [![GoDoc](https://godoc.org/github.com/kezhuw/toml?status.svg)](http://godoc.org/github.com/kezhuw/toml) 6 | [![Build Status](https://travis-ci.org/kezhuw/toml.svg?branch=master)](https://travis-ci.org/kezhuw/toml) 7 | 8 | Run `go get github.com/kezhuw/toml` to install. 9 | 10 | ## Examples 11 | Snippets copied from Go test files. 12 | 13 | ```go 14 | package toml_test 15 | 16 | import ( 17 | "fmt" 18 | "time" 19 | 20 | "github.com/kezhuw/toml" 21 | ) 22 | 23 | func ExampleUnmarshal_integer() { 24 | data := []byte(`key = 12345`) 25 | var out struct{ Key int } 26 | 27 | err := toml.Unmarshal(data, &out) 28 | if err != nil { 29 | panic(err) 30 | } 31 | 32 | fmt.Println(out.Key) 33 | // Output: 12345 34 | } 35 | 36 | func ExampleUnmarshal_datetimeNative() { 37 | data := []byte(`key = 2016-01-07T15:30:30.123456789Z`) 38 | var out struct{ Key time.Time } 39 | 40 | err := toml.Unmarshal(data, &out) 41 | if err != nil { 42 | panic(err) 43 | } 44 | 45 | fmt.Println(out.Key.Format(time.RFC3339Nano)) 46 | // Output: 2016-01-07T15:30:30.123456789Z 47 | } 48 | 49 | func ExampleUnmarshal_array() { 50 | data := []byte(`key = [1, 2, 3,4]`) 51 | var out struct{ Key []int } 52 | 53 | err := toml.Unmarshal(data, &out) 54 | if err != nil { 55 | panic(err) 56 | } 57 | 58 | fmt.Println(out.Key) 59 | // Output: [1 2 3 4] 60 | } 61 | 62 | func ExampleUnmarshal_table() { 63 | data := []byte(`[key] 64 | name = "name" 65 | value = "value"`) 66 | var out struct { 67 | Key struct { 68 | Name string 69 | Value string 70 | } 71 | } 72 | 73 | err := toml.Unmarshal(data, &out) 74 | if err != nil { 75 | panic(err) 76 | } 77 | 78 | fmt.Printf("key.name = %q\n", out.Key.Name) 79 | fmt.Printf("key.value = %q\n", out.Key.Value) 80 | // Output: 81 | // key.name = "name" 82 | // key.value = "value" 83 | } 84 | 85 | func ExampleUnmarshal_inlineTable() { 86 | data := []byte(`key = { name = "name", value = "value" }`) 87 | var out struct { 88 | Key struct { 89 | Name string 90 | Value string 91 | } 92 | } 93 | 94 | err := toml.Unmarshal(data, &out) 95 | if err != nil { 96 | panic(err) 97 | } 98 | 99 | fmt.Printf("key.name = %q\n", out.Key.Name) 100 | fmt.Printf("key.value = %q\n", out.Key.Value) 101 | // Output: 102 | // key.name = "name" 103 | // key.value = "value" 104 | } 105 | 106 | func ExampleUnmarshal_tableArray() { 107 | data := []byte(` 108 | [[array]] 109 | description = "Table In Array" 110 | `) 111 | var out struct { 112 | Array []struct{ Description string } 113 | } 114 | 115 | err := toml.Unmarshal(data, &out) 116 | if err != nil { 117 | panic(err) 118 | } 119 | 120 | fmt.Println(out.Array[0].Description) 121 | // Output: Table In Array 122 | } 123 | 124 | func ExampleUnmarshal_interface() { 125 | data := []byte(`key = [1, 2, 3, 4,]`) 126 | var out struct{ Key interface{} } 127 | 128 | err := toml.Unmarshal(data, &out) 129 | if err != nil { 130 | panic(err) 131 | } 132 | 133 | fmt.Println(out.Key) 134 | // Output: [1 2 3 4] 135 | } 136 | 137 | func ExampleUnmarshal_tagName() { 138 | data := []byte(`KKKK = "value"`) 139 | var out struct { 140 | Key string `toml:"KKKK"` 141 | } 142 | 143 | err := toml.Unmarshal(data, &out) 144 | if err != nil { 145 | panic(err) 146 | } 147 | 148 | fmt.Println(out.Key) 149 | // Output: value 150 | } 151 | 152 | func ExampleUnmarshal_tagIgnore() { 153 | data := []byte(`key = "value"`) 154 | var out struct { 155 | Key string `toml:"-"` 156 | } 157 | 158 | err := toml.Unmarshal(data, &out) 159 | if err != nil { 160 | panic(err) 161 | } 162 | 163 | fmt.Println(out.Key) 164 | // Output: 165 | } 166 | 167 | func ExampleUnmarshal_tagString() { 168 | data := []byte(`key = "12345"`) 169 | var out struct { 170 | Key int `toml:",string"` 171 | } 172 | 173 | err := toml.Unmarshal(data, &out) 174 | if err != nil { 175 | panic(err) 176 | } 177 | 178 | fmt.Println(out.Key) 179 | // Output: 12345 180 | } 181 | 182 | func ExampleUnmarshal_tagOmitempty() { 183 | data := []byte(``) 184 | var out struct { 185 | Key string `toml:",omitempty"` 186 | } 187 | out.Key = "Not empty, for now." 188 | 189 | err := toml.Unmarshal(data, &out) 190 | if err != nil { 191 | panic(err) 192 | } 193 | 194 | fmt.Println(out.Key) 195 | // Output: 196 | } 197 | ``` 198 | 199 | ```go 200 | package toml_test 201 | 202 | import ( 203 | "fmt" 204 | "time" 205 | 206 | "github.com/kezhuw/toml" 207 | ) 208 | 209 | type duration time.Duration 210 | 211 | func (d *duration) UnmarshalText(b []byte) error { 212 | v, err := time.ParseDuration(string(b)) 213 | if err != nil { 214 | return err 215 | } 216 | *d = duration(v) 217 | return nil 218 | } 219 | 220 | func ExampleUnmarshal_textUnmarshaler() { 221 | data := []byte(`timeout = "300ms"`) 222 | var out struct{ Timeout duration } 223 | 224 | err := toml.Unmarshal(data, &out) 225 | if err != nil { 226 | panic(err) 227 | } 228 | 229 | fmt.Println(time.Duration(out.Timeout)) 230 | // Output: 300ms 231 | } 232 | ``` 233 | 234 | ## Links 235 | Other TOML libraries written in Go. 236 | 237 | - https://github.com/BurntSushi/toml TOML parser and encoder for Go with reflection 238 | - https://github.com/pelletier/go-toml Go library for the TOML language 239 | - https://github.com/naoina/toml TOML parser and encoder library for Golang 240 | 241 | ## License 242 | Released under The MIT License (MIT). See [LICENSE](LICENSE) for the full license text. 243 | 244 | ## Contribution 245 | Fire issue or pull request if you have any questions. 246 | 247 | [TOML]: https://github.com/toml-lang/toml 248 | -------------------------------------------------------------------------------- /decode.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "encoding" 5 | "encoding/base64" 6 | "fmt" 7 | "go/ast" 8 | "reflect" 9 | "runtime" 10 | "strconv" 11 | "strings" 12 | "time" 13 | 14 | "github.com/kezhuw/toml/internal/types" 15 | ) 16 | 17 | // An InvalidUnmarshalError describes that an invalid argment was passed 18 | // to Unmarshal. The argument passed to Unmarshal must be non-nil pointer. 19 | type InvalidUnmarshalError struct { 20 | Type reflect.Type 21 | } 22 | 23 | func (e *InvalidUnmarshalError) Error() string { 24 | if e.Type == nil { 25 | return "toml: Unmarshal(nil)" 26 | } 27 | if e.Type.Kind() != reflect.Ptr { 28 | return "toml: Unmarshal(non-pointer " + e.Type.String() + ")" 29 | } 30 | return "toml: Unmarshal(nil " + e.Type.String() + ")" 31 | } 32 | 33 | // An UnmarshalTypeError describes that a TOML value is not appropriate 34 | // to be stored in specified Go type. 35 | type UnmarshalTypeError struct { 36 | Value string 37 | Type reflect.Type 38 | } 39 | 40 | func (e *UnmarshalTypeError) Error() string { 41 | return "toml: cannot unmarshal " + e.Value + " to Go value of type " + e.Type.String() 42 | } 43 | 44 | // An UnmarshalOverflowError describes that a TOML number value overflows 45 | // specified Go type. 46 | type UnmarshalOverflowError struct { 47 | Value string 48 | Type reflect.Type 49 | } 50 | 51 | func (e *UnmarshalOverflowError) Error() string { 52 | return "toml: " + e.Value + " overflow Go value of type " + e.Type.String() 53 | } 54 | 55 | func indirectType(t reflect.Type) reflect.Type { 56 | for t.Kind() == reflect.Ptr { 57 | t = t.Elem() 58 | } 59 | return t 60 | } 61 | 62 | func indirectValue(v reflect.Value) (encoding.TextUnmarshaler, reflect.Value) { 63 | var u encoding.TextUnmarshaler 64 | for { 65 | if v.Kind() == reflect.Interface && !v.IsNil() { 66 | e := v.Elem() 67 | if e.Kind() == reflect.Ptr && !e.IsNil() { 68 | v = e 69 | continue 70 | } 71 | } 72 | if v.Kind() != reflect.Ptr { 73 | break 74 | } 75 | if v.IsNil() { 76 | v.Set(reflect.New(v.Type().Elem())) 77 | } 78 | if v.NumMethod() > 0 { 79 | // TOML has native Datetime support, while time.Time implements 80 | // encoding.TextUnmarshaler. For native Datetime, we need settable 81 | // time.Time struct, so continue here. 82 | if i, ok := v.Interface().(encoding.TextUnmarshaler); ok { 83 | u = i 84 | } 85 | } 86 | v = v.Elem() 87 | } 88 | return u, v 89 | } 90 | 91 | func findField(t *types.Table, field *reflect.StructField, tagname string) (string, types.Value) { 92 | if tagname != "" { 93 | return tagname, t.Elems[tagname] 94 | } 95 | if value, ok := t.Elems[field.Name]; ok { 96 | return field.Name, value 97 | } 98 | lowerName := strings.ToLower(field.Name) 99 | return lowerName, t.Elems[lowerName] 100 | } 101 | 102 | func unmarshalBoolean(b bool, v reflect.Value) { 103 | switch v.Kind() { 104 | case reflect.Bool: 105 | v.SetBool(b) 106 | case reflect.Interface: 107 | if v.NumMethod() == 0 { 108 | v.Set(reflect.ValueOf(b)) 109 | return 110 | } 111 | fallthrough 112 | default: 113 | panic(&UnmarshalTypeError{"boolean " + strconv.FormatBool(b), v.Type()}) 114 | } 115 | } 116 | 117 | func unmarshalQuoted(s string, v reflect.Value) { 118 | switch v.Kind() { 119 | case reflect.Bool: 120 | b, err := strconv.ParseBool(s) 121 | if err != nil { 122 | panic(err) 123 | } 124 | v.SetBool(b) 125 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 126 | i, err := strconv.ParseInt(s, 10, 64) 127 | if err != nil { 128 | panic(err) 129 | } 130 | if v.OverflowInt(i) { 131 | goto overflowError 132 | } 133 | v.SetInt(i) 134 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 135 | u, err := strconv.ParseUint(s, 10, 64) 136 | if err != nil { 137 | panic(err) 138 | } 139 | if v.OverflowUint(u) { 140 | goto overflowError 141 | } 142 | v.SetUint(u) 143 | case reflect.Float32, reflect.Float64: 144 | f, err := strconv.ParseFloat(s, 64) 145 | if err != nil { 146 | panic(err) 147 | } 148 | if v.OverflowFloat(f) { 149 | goto overflowError 150 | } 151 | v.SetFloat(f) 152 | default: 153 | panic("toml: unexpected type for quoted string") 154 | } 155 | return 156 | overflowError: 157 | panic(&UnmarshalOverflowError{"string " + strconv.Quote(s), v.Type()}) 158 | } 159 | 160 | func unmarshalString(s string, v reflect.Value, options tagOptions) { 161 | if v.Kind() != reflect.Ptr && v.CanAddr() { 162 | v = v.Addr() 163 | } 164 | u, v := indirectValue(v) 165 | if u != nil { 166 | err := u.UnmarshalText([]byte(s)) 167 | if err != nil { 168 | panic(err) 169 | } 170 | if v.Type().ConvertibleTo(datetimeType) { 171 | t := v.Addr().Convert(reflect.PtrTo(datetimeType)).Interface().(*time.Time) 172 | if t.IsZero() { 173 | *t = time.Time{} 174 | } 175 | } 176 | return 177 | } 178 | switch v.Kind() { 179 | case reflect.String: 180 | if options.Has("string") { 181 | t, err := strconv.Unquote(s) 182 | if err != nil { 183 | panic(err) 184 | } 185 | v.SetString(t) 186 | break 187 | } 188 | v.SetString(s) 189 | case reflect.Slice: 190 | if v.Type().Elem().Kind() != reflect.Uint8 { 191 | goto typeError 192 | } 193 | b := make([]byte, base64.StdEncoding.DecodedLen(len(s))) 194 | n, err := base64.StdEncoding.Decode(b, []byte(s)) 195 | if err != nil { 196 | panic(err) 197 | } 198 | v.SetBytes(b[:n]) 199 | case reflect.Bool, 200 | reflect.Float32, reflect.Float64, 201 | reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 202 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 203 | if !options.Has("string") { 204 | goto typeError 205 | } 206 | unmarshalQuoted(s, v) 207 | case reflect.Interface: 208 | if v.NumMethod() != 0 { 209 | goto typeError 210 | } 211 | v.Set(reflect.ValueOf(s)) 212 | default: 213 | goto typeError 214 | } 215 | return 216 | typeError: 217 | panic(&UnmarshalTypeError{fmt.Sprintf("string: %q", s), v.Type()}) 218 | } 219 | 220 | func unmarshalDatetime(t time.Time, v reflect.Value) { 221 | if t.IsZero() { 222 | t = time.Time{} 223 | } 224 | if !reflect.TypeOf(t).ConvertibleTo(v.Type()) { 225 | panic(&UnmarshalTypeError{"datetime " + t.Format(time.RFC3339Nano), v.Type()}) 226 | } 227 | v.Set(reflect.ValueOf(t).Convert(v.Type())) 228 | } 229 | 230 | func unmarshalFloat(f float64, v reflect.Value) { 231 | switch v.Kind() { 232 | case reflect.Float32, reflect.Float64: 233 | if v.OverflowFloat(f) { 234 | panic(&UnmarshalOverflowError{"float " + strconv.FormatFloat(f, 'g', -1, 64), v.Type()}) 235 | } 236 | v.SetFloat(f) 237 | case reflect.Interface: 238 | if v.NumMethod() == 0 { 239 | v.Set(reflect.ValueOf(f)) 240 | return 241 | } 242 | fallthrough 243 | default: 244 | panic(&UnmarshalTypeError{"float " + strconv.FormatFloat(f, 'g', -1, 64), v.Type()}) 245 | } 246 | } 247 | 248 | func unmarshalInteger(i int64, v reflect.Value) { 249 | switch v.Kind() { 250 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 251 | if v.OverflowInt(i) { 252 | panic(&UnmarshalOverflowError{"integer " + strconv.FormatInt(i, 10), v.Type()}) 253 | } 254 | v.SetInt(i) 255 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 256 | if i < 0 { 257 | panic(&UnmarshalOverflowError{"integer " + strconv.FormatInt(i, 10), v.Type()}) 258 | } 259 | u := uint64(i) 260 | if v.OverflowUint(u) { 261 | panic(&UnmarshalOverflowError{"integer " + strconv.FormatUint(u, 10), v.Type()}) 262 | } 263 | v.SetUint(u) 264 | case reflect.Interface: 265 | if v.NumMethod() == 0 { 266 | v.Set(reflect.ValueOf(i)) 267 | return 268 | } 269 | fallthrough 270 | default: 271 | panic(&UnmarshalTypeError{"integer " + strconv.FormatInt(i, 10), v.Type()}) 272 | } 273 | } 274 | 275 | var emptyInterfaceType = reflect.TypeOf((*interface{})(nil)).Elem() 276 | 277 | func unmarshalMap(t *types.Table, v reflect.Value) { 278 | keyType := v.Type().Key() 279 | if keyType.Kind() != reflect.String { 280 | panic(&UnmarshalTypeError{"table", v.Type()}) 281 | } 282 | m := reflect.MakeMap(v.Type()) 283 | elemType := v.Type().Elem() 284 | elemZero := reflect.Zero(elemType) 285 | elemValue := reflect.New(elemType).Elem() 286 | for key, value := range t.Elems { 287 | elemValue.Set(elemZero) 288 | unmarshalValue(value, elemValue, nil) 289 | m.SetMapIndex(reflect.ValueOf(key).Convert(keyType), elemValue) 290 | } 291 | v.Set(m) 292 | } 293 | 294 | func unmarshalStructNested(t *types.Table, v reflect.Value, matchs map[string]struct{}) { 295 | _, v = indirectValue(v) 296 | vType := v.Type() 297 | for i := 0; i < v.NumField(); i++ { 298 | field := vType.Field(i) 299 | name, options := parseTag(field.Tag.Get("toml")) 300 | if name == "-" { 301 | continue 302 | } 303 | // Don't look inside explicitly tagged anonymous struct fields. 304 | if field.Anonymous && name == "" { 305 | fieldValue := v.Field(i) 306 | switch field.Type.Kind() { 307 | case reflect.Struct: 308 | unmarshalStructNested(t, v.Field(i), matchs) 309 | continue 310 | case reflect.Ptr: 311 | if field.Type.Elem().Kind() != reflect.Struct { 312 | break 313 | } 314 | if fieldValue.IsNil() { 315 | fieldNew := reflect.New(field.Type.Elem()) 316 | n := len(matchs) 317 | unmarshalStructNested(t, fieldNew.Elem(), matchs) 318 | if n != len(matchs) { 319 | fieldValue.Set(fieldNew) 320 | } 321 | } else { 322 | unmarshalStructNested(t, fieldValue, matchs) 323 | } 324 | continue 325 | } 326 | } 327 | // There is a bug which cause unexported embedded struct fields 328 | // settable in Go 1.5 and prior. See https://github.com/golang/go/issues/12367. 329 | // Use ast.IsExported to circumvent it. 330 | if !ast.IsExported(field.Name) { 331 | continue 332 | } 333 | name, value := findField(t, &field, name) 334 | if value == nil { 335 | if options.Has("omitempty") { 336 | v.Field(i).Set(reflect.Zero(field.Type)) 337 | } 338 | continue 339 | } 340 | if _, ok := matchs[name]; ok { 341 | continue 342 | } 343 | unmarshalValue(value, v.Field(i), options) 344 | matchs[name] = struct{}{} 345 | } 346 | } 347 | 348 | func unmarshalStruct(t *types.Table, v reflect.Value) { 349 | unmarshalStructNested(t, v, make(map[string]struct{}, len(t.Elems))) 350 | } 351 | 352 | func unmarshalTable(t *types.Table, v reflect.Value) { 353 | switch v.Kind() { 354 | case reflect.Map: 355 | unmarshalMap(t, v) 356 | case reflect.Struct: 357 | unmarshalStruct(t, v) 358 | case reflect.Interface: 359 | if v.NumMethod() == 0 { 360 | v.Set(reflect.ValueOf(t.Interface())) 361 | return 362 | } 363 | fallthrough 364 | default: 365 | panic(&UnmarshalTypeError{"table", v.Type()}) 366 | } 367 | } 368 | 369 | func unmarshalSlice(a *types.Array, v reflect.Value) { 370 | n := len(a.Elems) 371 | slice := reflect.MakeSlice(v.Type(), n, n) 372 | for i, value := range a.Elems { 373 | unmarshalValue(value, slice.Index(i), nil) 374 | } 375 | v.Set(slice) 376 | } 377 | 378 | func unmarshalGoArray(a *types.Array, v reflect.Value) { 379 | if len(a.Elems) != v.Type().Len() { 380 | panic(&UnmarshalTypeError{fmt.Sprintf("[%d]array", len(a.Elems)), v.Type()}) 381 | } 382 | if v.IsNil() { 383 | v.Set(reflect.Zero(v.Type())) 384 | } 385 | for i, value := range a.Elems { 386 | unmarshalValue(value, v.Index(i), nil) 387 | } 388 | } 389 | 390 | func unmarshalArray(a *types.Array, v reflect.Value) { 391 | switch v.Kind() { 392 | case reflect.Array: 393 | unmarshalGoArray(a, v) 394 | case reflect.Slice: 395 | unmarshalSlice(a, v) 396 | case reflect.Interface: 397 | if v.NumMethod() == 0 { 398 | v.Set(reflect.ValueOf(a.Interface())) 399 | return 400 | } 401 | fallthrough 402 | default: 403 | panic(&UnmarshalTypeError{"array", v.Type()}) 404 | } 405 | } 406 | 407 | func unmarshalValue(tv types.Value, rv reflect.Value, options tagOptions) { 408 | _, rv = indirectValue(rv) 409 | switch tv := tv.(type) { 410 | case types.Boolean: 411 | unmarshalBoolean(bool(tv), rv) 412 | case types.Float: 413 | unmarshalFloat(float64(tv), rv) 414 | case types.String: 415 | unmarshalString(string(tv), rv, options) 416 | case types.Integer: 417 | unmarshalInteger(int64(tv), rv) 418 | case types.Datetime: 419 | unmarshalDatetime(time.Time(tv), rv) 420 | case *types.Array: 421 | unmarshalArray(tv, rv) 422 | case *types.Table: 423 | unmarshalTable(tv, rv) 424 | } 425 | } 426 | 427 | func catchError(errp *error) { 428 | if r := recover(); r != nil { 429 | switch err := r.(type) { 430 | default: 431 | panic(r) 432 | case runtime.Error: 433 | panic(r) 434 | case error: 435 | *errp = err 436 | } 437 | } 438 | } 439 | 440 | // Unmarshal parses TOML data and stores the result in the value pointed 441 | // by v. 442 | // 443 | // To unmarshal TOML into a struct, Unmarshal uses TOML tagged name to 444 | // find matching item in TOML table. Field name and its lower case will 445 | // got tried in sequence if TOML tagged name is absent. Options can be 446 | // specified after tag name separated by comma. Examples: 447 | // 448 | // // Field is ignored by this package. 449 | // Field int `toml:"-"` 450 | // 451 | // // "Field" and "field" will be used to find key in TOML table. 452 | // Field int `toml:"myName"` 453 | // 454 | // // "myName" will be used to find matching item in TOML table, and 455 | // // if it is absent, it will be set to zero value. 456 | // Field int `toml:"myName,omitempty"` 457 | // 458 | // // "Field" and "field" will be used to find key in TOML table and 459 | // // this field can be unmarshalled from TOML string. 460 | // Field int `toml:",string" 461 | // 462 | // To unmarshal TOML into an interface value, Unmarshal stores TOML 463 | // value in following types: 464 | // 465 | // bool, for TOML Boolean 466 | // int64, for TOML Integer 467 | // float64, for TOML Float 468 | // string, for TOML String 469 | // time.Time, for TOML Datetime 470 | // []interface{}, for TOML Array 471 | // map[string]interface{}, for TOML Table 472 | // 473 | // There is no guarantee that origin data in Go value will be preserved 474 | // after a failure or success Unmarshal(). 475 | func Unmarshal(data []byte, v interface{}) (err error) { 476 | defer catchError(&err) 477 | 478 | t, err := parse(data) 479 | if err != nil { 480 | return err 481 | } 482 | 483 | rv := reflect.ValueOf(v) 484 | if rv.Kind() != reflect.Ptr || rv.IsNil() { 485 | return &InvalidUnmarshalError{reflect.TypeOf(v)} 486 | } 487 | 488 | _, rv = indirectValue(rv) 489 | unmarshalTable(t, rv) 490 | return nil 491 | } 492 | -------------------------------------------------------------------------------- /decode_test.go: -------------------------------------------------------------------------------- 1 | package toml_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "time" 7 | 8 | "github.com/kezhuw/toml" 9 | ) 10 | 11 | type embed0 struct { 12 | E0 int `toml:"embed0"` 13 | } 14 | 15 | type Embed1 struct { 16 | embed0 17 | } 18 | 19 | type Embeda struct { 20 | Ea string 21 | } 22 | 23 | type Embedb struct { 24 | *Embeda 25 | } 26 | 27 | type Integer16 uint16 28 | 29 | type Struct struct { 30 | Integer16 31 | STRING string `toml:"sTrInG"` 32 | Pointer *string 33 | Nested Embeda 34 | } 35 | 36 | type Unicode struct { 37 | Welcome string `toml:"初次见面"` 38 | } 39 | 40 | type Ignore struct { 41 | Key string `toml:"-"` 42 | } 43 | 44 | type IgnoreEmbed struct { 45 | embed0 `toml:"-"` 46 | Embeda 47 | } 48 | 49 | type String struct { 50 | Integer int64 `toml:",string"` 51 | } 52 | 53 | type Overflow struct { 54 | Uint8 uint8 55 | Float32 float32 56 | } 57 | 58 | type Datetime struct { 59 | T time.Time 60 | } 61 | 62 | type Types struct { 63 | Int int 64 | Float float64 65 | String string 66 | } 67 | 68 | type Omitempty struct { 69 | S string `toml:",omitempty"` 70 | } 71 | 72 | var nonempty = Omitempty{S: "nonempty"} 73 | 74 | type testData struct { 75 | in string 76 | ptr interface{} 77 | out interface{} 78 | err error 79 | } 80 | 81 | var s = string("string") 82 | 83 | var unmarshalTests = []testData{ 84 | {`embed0 = 3_456`, new(Embed1), Embed1{embed0{E0: 3456}}, nil}, 85 | {`ea = """ea"""`, new(Embedb), Embedb{&Embeda{"ea"}}, nil}, 86 | {"sTrInG = 'ip4'\n integer16 = 1234", new(Struct), Struct{STRING: "ip4", Integer16: 1234}, nil}, 87 | {"sTrInG = '''ip6'''\ninteger16 = 1234", new(Struct), Struct{STRING: "ip6", Integer16: 1234}, nil}, 88 | {`pointer = "string"`, new(Struct), Struct{Pointer: &s}, nil}, 89 | {`"初次见面" = "你好,世界!"`, new(Unicode), Unicode{"你好,世界!"}, nil}, 90 | {`"初次\u89c1\U00009762" = "你好,\u4e16\U0000754c!"`, new(Unicode), Unicode{"你好,世界!"}, nil}, 91 | {`t = 2016-01-07T15:30:30Z`, new(Datetime), Datetime{time.Date(2016, 1, 7, 15, 30, 30, 0, time.UTC)}, nil}, 92 | {`t = "2016-01-07T15:30:30Z"`, new(Datetime), Datetime{time.Date(2016, 1, 7, 15, 30, 30, 0, time.UTC)}, nil}, 93 | {`Key = "ignored"`, new(Ignore), Ignore{}, nil}, 94 | {`embed0 = 34_344_532`, new(IgnoreEmbed), IgnoreEmbed{}, nil}, 95 | {`integer = "123456"`, new(String), String{123456}, nil}, 96 | {``, &nonempty, Omitempty{}, nil}, 97 | { 98 | in: `uint8 = 257`, 99 | ptr: new(Overflow), 100 | err: &toml.UnmarshalOverflowError{"integer 257", reflect.TypeOf(uint8(0))}, 101 | }, 102 | { 103 | in: `uint8 = -1`, 104 | ptr: new(Overflow), 105 | err: &toml.UnmarshalOverflowError{"integer -1", reflect.TypeOf(uint8(0))}, 106 | }, 107 | { 108 | in: `float32 = 3.4e+49`, 109 | ptr: new(Overflow), 110 | err: &toml.UnmarshalOverflowError{"float 3.4e+49", reflect.TypeOf(float32(0))}, 111 | }, 112 | { 113 | in: `int = "string"`, 114 | ptr: new(Types), 115 | err: &toml.UnmarshalTypeError{`string: "string"`, reflect.TypeOf(int(0))}, 116 | }, 117 | { 118 | in: `string = 233`, 119 | ptr: new(Types), 120 | err: &toml.UnmarshalTypeError{`integer 233`, reflect.TypeOf(string(""))}, 121 | }, 122 | { 123 | in: `int = 233.5`, 124 | ptr: new(Types), 125 | err: &toml.UnmarshalTypeError{`float 233.5`, reflect.TypeOf(int(0))}, 126 | }, 127 | { 128 | in: ` 129 | [Nested] 130 | Ea = ''' 131 | Ea \ 132 | value''' 133 | `, 134 | ptr: new(Struct), 135 | out: Struct{Nested: Embeda{Ea: "Ea \\\nvalue"}}, 136 | }, 137 | { 138 | in: `Nested = { Ea = """E\ 139 | a value""" }`, 140 | ptr: new(Struct), 141 | out: Struct{Nested: Embeda{Ea: "Ea value"}}, 142 | }, 143 | { 144 | in: ` 145 | integers = [ 1, 2, 3, 4,] 146 | [[tables]] 147 | description = "I am a TOML table" 148 | [[tables]] 149 | name = "Another TOML table" 150 | `, 151 | ptr: new(interface{}), 152 | out: map[string]interface{}{ 153 | "integers": []interface{}{int64(1), int64(2), int64(3), int64(4)}, 154 | "tables": []interface{}{ 155 | map[string]interface{}{"description": "I am a TOML table"}, 156 | map[string]interface{}{"name": "Another TOML table"}, 157 | }, 158 | }, 159 | }, 160 | { 161 | in: `[[table.array]]`, 162 | ptr: new(interface{}), 163 | out: map[string]interface{}{ 164 | "table": map[string]interface{}{ 165 | "array": []interface{}{ 166 | map[string]interface{}{}, 167 | }, 168 | }, 169 | }, 170 | }, 171 | { 172 | in: ` 173 | points = [ { x = 1, y = 2, z = 3 }, 174 | { x = 7, y = 8, z = 9 }, 175 | { x = 2, y = 4, z = 8 } ]`, 176 | ptr: new(interface{}), 177 | out: map[string]interface{}{ 178 | "points": []interface{}{ 179 | map[string]interface{}{ 180 | "x": int64(1), "y": int64(2), "z": int64(3), 181 | }, 182 | map[string]interface{}{ 183 | "x": int64(7), "y": int64(8), "z": int64(9), 184 | }, 185 | map[string]interface{}{ 186 | "x": int64(2), "y": int64(4), "z": int64(8), 187 | }, 188 | }, 189 | }, 190 | }, 191 | { 192 | in: ` 193 | [[fruit]] 194 | name = "apple" 195 | 196 | [fruit.physical] 197 | color = "red" 198 | shape = "round" 199 | 200 | [[fruit.variety]] 201 | name = "red delicious" 202 | 203 | [[fruit.variety]] 204 | name = "granny smith" 205 | 206 | [[fruit]] 207 | name = "banana" 208 | 209 | [[fruit.variety]] 210 | name = "plantain" 211 | `, 212 | ptr: new(interface{}), 213 | out: map[string]interface{}{ 214 | "fruit": []interface{}{ 215 | map[string]interface{}{ 216 | "name": "apple", 217 | "physical": map[string]interface{}{ 218 | "color": "red", 219 | "shape": "round", 220 | }, 221 | "variety": []interface{}{ 222 | map[string]interface{}{"name": "red delicious"}, 223 | map[string]interface{}{"name": "granny smith"}, 224 | }, 225 | }, 226 | map[string]interface{}{ 227 | "name": "banana", 228 | "variety": []interface{}{ 229 | map[string]interface{}{"name": "plantain"}, 230 | }, 231 | }, 232 | }, 233 | }, 234 | }, 235 | { 236 | in: ` 237 | [[products]] 238 | name = "Hammer" 239 | sku = 738594937 240 | 241 | [[products]] 242 | 243 | [[products]] 244 | name = "Nail" 245 | sku = 284758393 246 | color = "gray" 247 | `, 248 | ptr: new(interface{}), 249 | out: map[string]interface{}{ 250 | "products": []interface{}{ 251 | map[string]interface{}{ 252 | "name": "Hammer", 253 | "sku": int64(738594937), 254 | }, 255 | map[string]interface{}{}, 256 | map[string]interface{}{ 257 | "name": "Nail", 258 | "sku": int64(284758393), 259 | "color": "gray", 260 | }, 261 | }, 262 | }, 263 | }, 264 | } 265 | 266 | func TestUnmarshal(t *testing.T) { 267 | for i, test := range unmarshalTests { 268 | err := toml.Unmarshal([]byte(test.in), test.ptr) 269 | 270 | if test.err != nil { 271 | if !reflect.DeepEqual(test.err, err) { 272 | t.Errorf("#%d: error got %s\n, want %s", i, err, test.err) 273 | } 274 | continue 275 | } 276 | 277 | if err != nil { 278 | t.Errorf("#%d: got error: %s", i, err) 279 | continue 280 | } 281 | 282 | got := reflect.ValueOf(test.ptr).Elem().Interface() 283 | if !reflect.DeepEqual(got, test.out) { 284 | t.Errorf("#%d: got %+v\n, want %+v", i, got, test.out) 285 | continue 286 | } 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /encode.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "bytes" 5 | "encoding" 6 | "encoding/base64" 7 | "fmt" 8 | "go/ast" 9 | "io" 10 | "reflect" 11 | "strconv" 12 | "strings" 13 | "time" 14 | "unicode/utf8" 15 | ) 16 | 17 | type encodeState struct { 18 | bytes.Buffer 19 | } 20 | 21 | // InvalidMarshalError describes that invalid argument passed to Marshal. 22 | type InvalidMarshalError struct { 23 | Type reflect.Type 24 | } 25 | 26 | func (e *InvalidMarshalError) Error() string { 27 | return "toml: Marshal(nil " + e.Type.String() + ")" 28 | } 29 | 30 | // InvalidUTF8Error describes that invalid UTF-8 encoded string encountered. 31 | type InvalidUTF8Error struct { 32 | S string 33 | } 34 | 35 | func (e *InvalidUTF8Error) Error() string { 36 | return "toml: invalid UTF-8 in string: " + strconv.Quote(e.S) 37 | } 38 | 39 | // StructKeyError describes that multiple fields in struct has conflicted key. 40 | type StructKeyError struct { 41 | Key string 42 | Type reflect.Type 43 | } 44 | 45 | func (e *StructKeyError) Error() string { 46 | return "toml: Go type " + e.Type.String() + " has conflicted key: " + e.Key 47 | } 48 | 49 | // MarshalTypeError describes that a value of specified Go type cannot 50 | // be represent as desired TOML type. 51 | type MarshalTypeError struct { 52 | Type reflect.Type 53 | As string 54 | } 55 | 56 | func (e *MarshalTypeError) Error() string { 57 | return "toml: cannot marshal Go value of type " + e.Type.String() + " as toml " + e.As 58 | } 59 | 60 | // MarshalArrayTypeError describes that an unexpected type of array element 61 | // was encountered. 62 | type MarshalArrayTypeError struct { 63 | Path string 64 | Expect string 65 | Got string 66 | } 67 | 68 | func (e *MarshalArrayTypeError) Error() string { 69 | return fmt.Sprintf("toml: array at %s expect element of type %s, got %s", e.Path, e.Expect, e.Got) 70 | } 71 | 72 | // MarshalNilValueError describes that a nil pointer or interface in array or slice. 73 | type MarshalNilValueError struct { 74 | Type reflect.Type 75 | As string 76 | } 77 | 78 | func (e *MarshalNilValueError) Error() string { 79 | return "toml: cannot marshal nil value of Go type " + e.Type.String() + " as toml " + e.As 80 | } 81 | 82 | func indirectPtr(v reflect.Value) (encoding.TextMarshaler, reflect.Value) { 83 | for (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && !v.IsNil() { 84 | v = v.Elem() 85 | } 86 | if v.CanInterface() { 87 | if i, ok := v.Interface().(encoding.TextMarshaler); ok { 88 | return i, v 89 | } 90 | } 91 | if v.Kind() != reflect.Ptr && v.CanAddr() { 92 | p := v.Addr() 93 | if i, ok := p.Interface().(encoding.TextMarshaler); ok { 94 | return i, v 95 | } 96 | } 97 | return nil, v 98 | } 99 | 100 | type field struct { 101 | key string 102 | value reflect.Value 103 | } 104 | 105 | type table struct { 106 | Inline bool 107 | Path string 108 | Type reflect.Type 109 | sep string 110 | keys map[string]struct{} 111 | tables []field // table or array of tables 112 | } 113 | 114 | func (t *table) fieldSep() string { 115 | sep := t.sep 116 | if sep == "" { 117 | t.sep = "\n" 118 | } else if sep == " " { 119 | t.sep = ", " 120 | } 121 | return sep 122 | } 123 | 124 | func (t *table) tableSep() string { 125 | sep := t.sep 126 | if sep == "" { 127 | t.sep = "\n" 128 | } else { 129 | return "\n\n" 130 | } 131 | return sep 132 | } 133 | 134 | func (t *table) recordKey(key string) { 135 | if t.keys == nil { 136 | return 137 | } 138 | if _, ok := t.keys[key]; ok { 139 | panic(&StructKeyError{Key: key, Type: t.Type}) 140 | } 141 | t.keys[key] = struct{}{} 142 | } 143 | 144 | func (t *table) appendStructField(key string, value reflect.Value) { 145 | t.recordKey(key) 146 | t.tables = append(t.tables, field{key, value}) 147 | } 148 | 149 | var ( 150 | datetimeType = reflect.TypeOf((*time.Time)(nil)).Elem() 151 | ) 152 | 153 | type stringValues []reflect.Value 154 | 155 | func (sv stringValues) Len() int { return len(sv) } 156 | func (sv stringValues) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] } 157 | func (sv stringValues) Less(i, j int) bool { return sv[i].String() < sv[j].String() } 158 | 159 | func (e *encodeState) WriteSepKeyAssign(sep, key string) { 160 | e.WriteString(sep) 161 | e.WriteString(normalizeKey(key)) 162 | e.WriteString(" = ") 163 | } 164 | 165 | func isASCIIString(s string) bool { 166 | for _, r := range s { 167 | if r >= utf8.RuneSelf { 168 | return false 169 | } 170 | } 171 | return true 172 | } 173 | 174 | func quoteBasic(s string, quotes string, ASCIIOnly bool) string { 175 | origin := s 176 | multiline := quotes == `"""` 177 | pendingQuotes := "" 178 | var runeTmp [utf8.UTFMax]byte 179 | buf := make([]byte, 0, 3*len(s)/2) 180 | for width := 0; len(s) > 0; s = s[width:] { 181 | width = 1 182 | r := rune(s[0]) 183 | if r >= utf8.RuneSelf { 184 | r, width = utf8.DecodeRuneInString(s) 185 | } 186 | if r == utf8.RuneError { 187 | panic(&InvalidUTF8Error{origin}) 188 | } 189 | if r == '"' { 190 | if !multiline || len(pendingQuotes) == 2 { 191 | buf = append(buf, `\"`...) 192 | continue 193 | } 194 | pendingQuotes += "\"" 195 | continue 196 | } else if pendingQuotes != "" { 197 | buf = append(buf, pendingQuotes...) 198 | } 199 | if r == '\\' { 200 | buf = append(buf, `\\`...) 201 | continue 202 | } 203 | if multiline && (r == '\r' || r == '\n') { 204 | buf = append(buf, byte(r)) 205 | continue 206 | } 207 | if ASCIIOnly { 208 | if r < utf8.RuneSelf && strconv.IsPrint(r) { 209 | buf = append(buf, byte(r)) 210 | continue 211 | } 212 | } else if strconv.IsPrint(r) { 213 | n := utf8.EncodeRune(runeTmp[:], r) 214 | buf = append(buf, runeTmp[:n]...) 215 | continue 216 | } 217 | switch { 218 | case r == '\b': 219 | buf = append(buf, `\b`...) 220 | case r == '\t': 221 | buf = append(buf, `\t`...) 222 | case r == '\f': 223 | buf = append(buf, `\f`...) 224 | case r == '\r': 225 | buf = append(buf, `\r`...) 226 | case r == '\n': 227 | buf = append(buf, `\n`...) 228 | case r < 0x10000: 229 | buf = append(buf, `\u`...) 230 | buf = append(buf, fmt.Sprintf("%04x", r)...) 231 | default: 232 | buf = append(buf, `\U`...) 233 | buf = append(buf, fmt.Sprintf("%08x", r)...) 234 | } 235 | } 236 | return string(buf) 237 | } 238 | 239 | func (e *encodeState) marshalBasicString(s string, options tagOptions) { 240 | e.WriteByte('"') 241 | e.WriteString(quoteBasic(s, "\"", options.Has("ascii"))) 242 | e.WriteByte('"') 243 | } 244 | 245 | func (e *encodeState) marshalMultilineString(s string, options tagOptions) { 246 | e.WriteString(`"""`) 247 | e.WriteByte('\n') 248 | e.WriteString(quoteBasic(s, `"""`, options.Has("ascii"))) 249 | e.WriteString(`"""`) 250 | } 251 | 252 | func (e *encodeState) marshalLiteralString(s string, options tagOptions) { 253 | if options.Has("ascii") && !isASCIIString(s) { 254 | e.marshalBasicString(s, options) 255 | return 256 | } 257 | e.WriteByte('\'') 258 | e.WriteString(s) 259 | e.WriteByte('\'') 260 | } 261 | 262 | func (e *encodeState) marshalMultilineLiteral(s string, options tagOptions) { 263 | if options.Has("ascii") && !isASCIIString(s) { 264 | e.marshalMultilineString(s, options) 265 | return 266 | } 267 | e.WriteString(`'''`) 268 | e.WriteByte('\n') 269 | e.WriteString(s) 270 | e.WriteString(`'''`) 271 | } 272 | 273 | func (e *encodeState) marshalStringValue(s string, options tagOptions) { 274 | if options.Has("multiline") { 275 | if options.Has("literal") { 276 | if strings.Index(s, `'''`) == -1 { 277 | e.marshalMultilineLiteral(s, options) 278 | return 279 | } 280 | } 281 | e.marshalMultilineString(s, options) 282 | return 283 | } 284 | if options.Has("literal") { 285 | if strings.IndexAny(s, "'\r\n") == -1 { 286 | e.marshalLiteralString(s, options) 287 | return 288 | } 289 | } 290 | e.marshalBasicString(s, options) 291 | } 292 | 293 | func stringQuote(options tagOptions) string { 294 | if options.Has("multiline") { 295 | if options.Has("literal") { 296 | return `'''` 297 | } 298 | return `"""` 299 | } 300 | if options.Has("literal") { 301 | return `'` 302 | } 303 | return `"` 304 | } 305 | 306 | func (e *encodeState) marshalBytesValue(b []byte, options tagOptions) { 307 | quote := stringQuote(options) 308 | e.WriteString(quote) 309 | enc := base64.NewEncoder(base64.StdEncoding, e) 310 | enc.Write(b) 311 | enc.Close() 312 | e.WriteString(quote) 313 | } 314 | 315 | func (e *encodeState) marshalTextValue(ti encoding.TextMarshaler, options tagOptions) { 316 | b, err := ti.MarshalText() 317 | if err != nil { 318 | panic(err) 319 | } 320 | e.marshalStringValue(string(b), options) 321 | } 322 | 323 | func (e *encodeState) marshalRawValue(v string, options tagOptions) { 324 | if options.Has("string") || options.Has("literal") { 325 | e.marshalStringValue(v, options) 326 | return 327 | } 328 | e.WriteString(v) 329 | } 330 | 331 | func (e *encodeState) marshalBoolValue(b bool, options tagOptions) { 332 | e.marshalRawValue(strconv.FormatBool(b), options) 333 | } 334 | 335 | func (e *encodeState) marshalIntValue(i int64, options tagOptions) { 336 | e.marshalRawValue(strconv.FormatInt(i, 10), options) 337 | } 338 | 339 | func (e *encodeState) marshalUintValue(u uint64, options tagOptions) { 340 | e.marshalRawValue(strconv.FormatUint(u, 10), options) 341 | } 342 | 343 | func (e *encodeState) marshalFloatValue(f float64, options tagOptions) { 344 | s := strconv.FormatFloat(f, 'g', -1, 64) 345 | if strings.IndexAny(s, ".e") == -1 { 346 | s += ".0" 347 | } 348 | e.marshalRawValue(s, options) 349 | } 350 | 351 | func (e *encodeState) marshalBoolField(t *table, key string, b bool, options tagOptions) { 352 | t.recordKey(key) 353 | e.WriteSepKeyAssign(t.fieldSep(), key) 354 | e.marshalBoolValue(b, options) 355 | } 356 | 357 | func (e *encodeState) marshalIntField(t *table, key string, i int64, options tagOptions) { 358 | t.recordKey(key) 359 | e.WriteSepKeyAssign(t.fieldSep(), key) 360 | e.marshalIntValue(i, options) 361 | } 362 | 363 | func (e *encodeState) marshalUintField(t *table, key string, u uint64, options tagOptions) { 364 | t.recordKey(key) 365 | e.WriteSepKeyAssign(t.fieldSep(), key) 366 | e.marshalUintValue(u, options) 367 | } 368 | 369 | func (e *encodeState) marshalFloatField(t *table, key string, f float64, options tagOptions) { 370 | t.recordKey(key) 371 | e.WriteSepKeyAssign(t.fieldSep(), key) 372 | e.marshalFloatValue(f, options) 373 | } 374 | 375 | func (e *encodeState) marshalStringField(t *table, key string, value string, options tagOptions) { 376 | t.recordKey(key) 377 | e.WriteSepKeyAssign(t.fieldSep(), key) 378 | e.marshalStringValue(value, options) 379 | } 380 | 381 | func (e *encodeState) marshalDatetimeValue(value reflect.Value, options tagOptions) { 382 | t := value.Convert(datetimeType).Interface().(time.Time) 383 | s := t.Format(time.RFC3339Nano) 384 | e.marshalRawValue(s, options) 385 | } 386 | 387 | func (e *encodeState) marshalDatetimeField(t *table, key string, value reflect.Value, options tagOptions) { 388 | t.recordKey(key) 389 | e.WriteSepKeyAssign(t.fieldSep(), key) 390 | e.marshalDatetimeValue(value, options) 391 | } 392 | 393 | func (e *encodeState) marshalTextField(t *table, key string, ti encoding.TextMarshaler, options tagOptions) { 394 | t.recordKey(key) 395 | e.WriteSepKeyAssign(t.fieldSep(), key) 396 | e.marshalTextValue(ti, options) 397 | } 398 | 399 | func isNilValue(v reflect.Value) bool { 400 | switch v.Type().Kind() { 401 | case reflect.Ptr, reflect.Interface, reflect.Map, reflect.Slice: 402 | return v.IsNil() 403 | } 404 | return false 405 | } 406 | 407 | func isEmptyValue(v reflect.Value) bool { 408 | switch v.Type().Kind() { 409 | case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 410 | return v.Len() == 0 411 | case reflect.Bool: 412 | return v.Bool() == false 413 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 414 | return v.Int() == 0 415 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 416 | return v.Uint() == 0 417 | case reflect.Float32, reflect.Float64: 418 | return v.Float() == 0 419 | case reflect.Ptr, reflect.Interface: 420 | return v.IsNil() 421 | } 422 | return false 423 | } 424 | 425 | func checkArrayElemType(path string, elemType string) func(newElemType string) { 426 | return func(newType string) { 427 | if elemType == "" { 428 | elemType = newType 429 | } else if elemType != newType { 430 | panic(&MarshalArrayTypeError{Path: path, Expect: elemType, Got: newType}) 431 | } 432 | } 433 | } 434 | 435 | func isTableType(typ reflect.Type) bool { 436 | return typ.Kind() == reflect.Map || typ.Kind() == reflect.Struct 437 | } 438 | 439 | func (e *encodeState) marshalArrayValue(path string, v reflect.Value, options tagOptions) string { 440 | if v.Type().Kind() == reflect.Slice && v.Type().Elem().Kind() == reflect.Uint8 { 441 | e.marshalBytesValue(v.Bytes(), options) 442 | return "string" 443 | } 444 | 445 | sep := " " 446 | e.WriteByte('[') 447 | check := checkArrayElemType(path, "") 448 | for i, n := 0, v.Len(); i < n; i++ { 449 | e.WriteString(sep) 450 | ti, elem := indirectPtr(v.Index(i)) 451 | switch { 452 | case elem.Type() == datetimeType, 453 | elem.Type().ConvertibleTo(datetimeType) && options.Has("datetime"): 454 | check("datetime") 455 | e.marshalDatetimeValue(elem, options) 456 | continue 457 | case ti != nil: 458 | check("string") 459 | e.marshalTextValue(ti, options) 460 | continue 461 | } 462 | switch elem.Kind() { 463 | case reflect.Bool: 464 | check("boolean") 465 | e.marshalBoolValue(elem.Bool(), options) 466 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 467 | check("integer") 468 | e.marshalIntValue(elem.Int(), options) 469 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 470 | check("integer") 471 | e.marshalUintValue(elem.Uint(), options) 472 | case reflect.Float32, reflect.Float64: 473 | check("float") 474 | e.marshalFloatValue(elem.Float(), options) 475 | case reflect.String: 476 | check("string") 477 | e.marshalStringValue(elem.String(), options) 478 | case reflect.Array, reflect.Slice: 479 | check(e.marshalArrayValue(combineIndexPath(path, i), elem, options)) 480 | case reflect.Map: 481 | check("table") 482 | e.marshalMapValue(combineIndexPath(path, i), elem, options) 483 | case reflect.Struct: 484 | check("table") 485 | e.marshalStructValue(combineIndexPath(path, i), elem, options) 486 | case reflect.Ptr, reflect.Interface: 487 | panic(&MarshalNilValueError{Type: elem.Type(), As: "array element"}) 488 | default: 489 | panic(&MarshalTypeError{Type: elem.Type(), As: "array element"}) 490 | } 491 | sep = ", " 492 | } 493 | e.WriteString(" ]") 494 | return "array" 495 | } 496 | 497 | func (e *encodeState) marshalArrayField(t *table, key string, v reflect.Value, options tagOptions) { 498 | if v.Len() != 0 { 499 | ti, elem := indirectPtr(v.Index(0)) 500 | if ti == nil && isTableType(elem.Type()) && !options.Has("inline") { 501 | t.appendStructField(key, v) 502 | return 503 | } 504 | } 505 | t.recordKey(key) 506 | e.WriteSepKeyAssign(t.fieldSep(), key) 507 | e.marshalArrayValue(combineKeyPath(t.Path, key), v, options) 508 | } 509 | 510 | func (e *encodeState) marshalTableField(t *table, key string, v reflect.Value, options tagOptions) { 511 | ti, v := indirectPtr(v) 512 | 513 | switch { 514 | case v.Type() == datetimeType, 515 | v.Type().ConvertibleTo(datetimeType) && options.Has("datetime"): 516 | e.marshalDatetimeField(t, key, v, options) 517 | return 518 | case ti != nil: 519 | e.marshalTextField(t, key, ti, options) 520 | return 521 | } 522 | 523 | if isNilValue(v) || (options.Has("omitempty") && isEmptyValue(v)) { 524 | return 525 | } 526 | 527 | switch v.Kind() { 528 | case reflect.Bool: 529 | e.marshalBoolField(t, key, v.Bool(), options) 530 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 531 | e.marshalIntField(t, key, v.Int(), options) 532 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 533 | e.marshalUintField(t, key, v.Uint(), options) 534 | case reflect.Float32, reflect.Float64: 535 | e.marshalFloatField(t, key, v.Float(), options) 536 | case reflect.String: 537 | e.marshalStringField(t, key, v.String(), options) 538 | case reflect.Array, reflect.Slice: 539 | e.marshalArrayField(t, key, v, options) 540 | case reflect.Map: 541 | if t.Inline || options.Has("inline") { 542 | e.marshalMapField(t, key, v) 543 | } else { 544 | t.appendStructField(key, v) 545 | } 546 | case reflect.Struct: 547 | if t.Inline || options.Has("inline") { 548 | e.marshalStructField(t, key, v) 549 | } else { 550 | t.appendStructField(key, v) 551 | } 552 | default: 553 | panic(&MarshalTypeError{Type: v.Type(), As: "value"}) 554 | } 555 | } 556 | 557 | func (e *encodeState) marshalMapValue(path string, v reflect.Value, options tagOptions) { 558 | if v.Type().Key().Kind() != reflect.String { 559 | panic(&MarshalTypeError{Type: v.Type(), As: "table key"}) 560 | } 561 | e.WriteByte('{') 562 | var keys stringValues = v.MapKeys() 563 | t := &table{Inline: true, Type: v.Type(), sep: " "} 564 | for _, k := range keys { 565 | e.marshalTableField(t, k.String(), v.MapIndex(k), nil) 566 | } 567 | e.WriteByte('}') 568 | } 569 | 570 | func (e *encodeState) marshalMapField(t *table, key string, v reflect.Value) { 571 | t.recordKey(key) 572 | e.WriteSepKeyAssign(t.fieldSep(), key) 573 | e.marshalMapValue(combineKeyPath(t.Path, key), v, nil) 574 | } 575 | 576 | func (e *encodeState) marshalStructValue(path string, v reflect.Value, options tagOptions) { 577 | t := &table{Inline: true, Path: path, Type: v.Type(), sep: " ", keys: make(map[string]struct{})} 578 | e.WriteByte('{') 579 | e.marshalStructTable(t, v) 580 | e.WriteByte('}') 581 | } 582 | 583 | func (e *encodeState) marshalStructField(t *table, key string, v reflect.Value) { 584 | t.recordKey(key) 585 | e.WriteSepKeyAssign(t.fieldSep(), key) 586 | e.marshalStructValue(combineKeyPath(t.Path, key), v, nil) 587 | } 588 | 589 | func (e *encodeState) marshalStructTable(t *table, v reflect.Value) { 590 | for i := 0; i < v.NumField(); i++ { 591 | sf := v.Type().Field(i) 592 | name, options := parseTag(sf.Tag.Get("toml")) 593 | if name == "-" { 594 | continue 595 | } 596 | if sf.Anonymous && name == "" { 597 | fieldValue := v.Field(i) 598 | switch sf.Type.Kind() { 599 | case reflect.Struct: 600 | e.marshalStructTable(t, fieldValue) 601 | continue 602 | case reflect.Ptr: 603 | if sf.Type.Elem().Kind() != reflect.Struct { 604 | break 605 | } 606 | if !fieldValue.IsNil() { 607 | e.marshalStructTable(t, fieldValue.Elem()) 608 | } 609 | continue 610 | } 611 | } 612 | if !ast.IsExported(sf.Name) { 613 | continue 614 | } 615 | if name == "" { 616 | name = sf.Name 617 | } 618 | e.marshalTableField(t, name, v.Field(i), options) 619 | } 620 | } 621 | 622 | func (e *encodeState) marshalTables(sup *table, tables []field) { 623 | for _, f := range tables { 624 | v := f.value 625 | path := combineKeyPath(sup.Path, f.key) 626 | switch v.Type().Kind() { 627 | case reflect.Map: 628 | e.WriteString(fmt.Sprintf("%s[%s]", sup.tableSep(), path)) 629 | e.marshalMap(path, v) 630 | case reflect.Struct: 631 | e.WriteString(fmt.Sprintf("%s[%s]", sup.tableSep(), path)) 632 | e.marshalStruct(path, v) 633 | case reflect.Array, reflect.Slice: 634 | for i, n := 0, v.Len(); i < n; i++ { 635 | e.WriteString(fmt.Sprintf("%s[[%s]]", sup.tableSep(), path)) 636 | ti, elem := indirectPtr(v.Index(i)) 637 | if ti != nil { 638 | panic(&MarshalTypeError{Type: elem.Type(), As: "table"}) 639 | } 640 | switch elem.Type().Kind() { 641 | case reflect.Map: 642 | e.marshalMap(path, elem) 643 | case reflect.Struct: 644 | e.marshalStruct(path, elem) 645 | case reflect.Ptr, reflect.Interface: 646 | panic(&MarshalNilValueError{Type: elem.Type(), As: "array element"}) 647 | default: 648 | panic(&MarshalTypeError{Type: elem.Type(), As: "table"}) 649 | } 650 | } 651 | default: 652 | panic("toml: unexpected postponed field") 653 | } 654 | } 655 | } 656 | 657 | func (e *encodeState) marshalMap(path string, v reflect.Value) { 658 | if v.Type().Key().Kind() != reflect.String { 659 | panic(&MarshalTypeError{Type: v.Type(), As: "table key"}) 660 | } 661 | t := &table{Path: path, Type: v.Type(), sep: "\n"} 662 | if path == "" { 663 | t.sep = "" 664 | } 665 | var keys stringValues = v.MapKeys() 666 | for _, k := range keys { 667 | e.marshalTableField(t, k.String(), v.MapIndex(k), nil) 668 | } 669 | e.marshalTables(t, t.tables) 670 | } 671 | 672 | func (e *encodeState) marshalStruct(path string, v reflect.Value) { 673 | t := &table{Path: path, Type: v.Type(), sep: "\n", keys: make(map[string]struct{})} 674 | if path == "" { 675 | t.sep = "" 676 | } 677 | e.marshalStructTable(t, v) 678 | e.marshalTables(t, t.tables) 679 | } 680 | 681 | func validMarshal(v interface{}) (reflect.Value, error) { 682 | ti, rv := indirectPtr(reflect.ValueOf(v)) 683 | if ti != nil { 684 | return reflect.Value{}, &MarshalTypeError{Type: reflect.TypeOf(v), As: "table"} 685 | } 686 | switch rv.Kind() { 687 | case reflect.Struct, reflect.Map: 688 | case reflect.Ptr, reflect.Interface: 689 | return reflect.Value{}, &InvalidMarshalError{reflect.TypeOf(v)} 690 | default: 691 | return reflect.Value{}, &MarshalTypeError{Type: reflect.TypeOf(v), As: "table"} 692 | } 693 | return rv, nil 694 | } 695 | 696 | // Marshal returns TOML encoding of v. 697 | // 698 | // Argument v must be of type struct/map or non-nil pointer or interface 699 | // to these types and must not implement encoding.TextMarshaler. 700 | // 701 | // Values implementing encoding.TextMarshaler are encoded as strings. 702 | // 703 | // Fields with nil value in struct or map are ignored. Nil maps or 704 | // slices in array are encoded as empty tables or arrays in TOML. Error 705 | // is raised when nil pointer or interface is encountered in array or 706 | // slice. 707 | // 708 | // Slice of byte is encoded as base64-encoded string. 709 | // 710 | // time.Time and types with "datetime" tagged and convertible to 711 | // time.Time are encoded as TOML Datetime. 712 | // 713 | // Any value that will be encoded as string can have "literal", 714 | // "multiline" and/or "ascii" tagged. 715 | // 716 | // Struct or map fields tagged with "inline" are encoded as inline table. 717 | // 718 | // Tag options specified for array or slice fields are inherited by their 719 | // elements. 720 | func Marshal(v interface{}) (b []byte, err error) { 721 | rv, err := validMarshal(v) 722 | if err != nil { 723 | return nil, err 724 | } 725 | 726 | defer catchError(&err) 727 | 728 | var e encodeState 729 | switch rv.Kind() { 730 | case reflect.Map: 731 | e.marshalMap("", rv) 732 | case reflect.Struct: 733 | e.marshalStruct("", rv) 734 | } 735 | e.WriteByte('\n') 736 | return e.Bytes(), nil 737 | } 738 | 739 | // Encoder writes TOML document to an output stream. 740 | type Encoder struct { 741 | w io.Writer 742 | err error 743 | } 744 | 745 | // NewEncoder creates a new encoder that writes to w. 746 | func NewEncoder(w io.Writer) *Encoder { 747 | return &Encoder{w: w} 748 | } 749 | 750 | // Encode writes TOML document of v to the underlying stream. 751 | func (enc *Encoder) Encode(v interface{}) error { 752 | if enc.err != nil { 753 | return enc.err 754 | } 755 | 756 | b, err := Marshal(v) 757 | if err != nil { 758 | return err 759 | } 760 | 761 | _, enc.err = enc.w.Write(b) 762 | return enc.err 763 | } 764 | -------------------------------------------------------------------------------- /encode_test.go: -------------------------------------------------------------------------------- 1 | package toml_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | "time" 7 | 8 | "github.com/kezhuw/toml" 9 | ) 10 | 11 | type EncodeIgnore struct { 12 | Export string 13 | } 14 | 15 | type EncodeNested struct { 16 | Nested string 17 | } 18 | 19 | type EncodeEmbed struct { 20 | Uint32 uint32 21 | *EncodeNested 22 | EncodeIgnore `toml:"-"` 23 | } 24 | 25 | type EncodeTable struct { 26 | S string `toml:"s"` 27 | } 28 | 29 | type EncodeStruct struct { 30 | Int int 31 | Uint uint `toml:"unsigned,string"` 32 | Float float64 33 | String string 34 | Zero int `toml:",omitempty"` 35 | Empty string `toml:",omitempty"` 36 | Notempty string `toml:",omitempty"` 37 | Date time.Time 38 | Ignored string `toml:"-"` 39 | unexported string 40 | *EncodeEmbed 41 | Strings []string 42 | Table EncodeTable 43 | Tables []*EncodeTable `toml:",omitempty"` 44 | Inlines []EncodeTable `toml:",inline"` 45 | } 46 | 47 | type encodeData struct { 48 | in interface{} 49 | out interface{} 50 | err error 51 | print bool 52 | } 53 | 54 | var marshalTests = []encodeData{ 55 | { 56 | in: EncodeStruct{}, 57 | out: &EncodeStruct{}, 58 | }, 59 | { 60 | in: EncodeStruct{ 61 | Strings: []string{"string a", "string b"}, 62 | Table: EncodeTable{ 63 | S: "string in table", 64 | }, 65 | Inlines: []EncodeTable{ 66 | {S: "inline table 1"}, 67 | {S: "inline table 2"}, 68 | }, 69 | Tables: []*EncodeTable{ 70 | &EncodeTable{S: "pointer type table 1"}, 71 | &EncodeTable{S: "pointer type table 2"}, 72 | }, 73 | }, 74 | out: new(EncodeStruct), 75 | }, 76 | { 77 | in: EncodeStruct{ 78 | Int: -3242, 79 | Uint: 9999329, 80 | Float: 3.3e9, 81 | String: "toml string", 82 | Notempty: "not empty", 83 | Date: time.Date(2016, 1, 7, 15, 30, 30, 0, time.UTC), 84 | Ignored: "ignore", 85 | unexported: "unexported", 86 | EncodeEmbed: &EncodeEmbed{ 87 | Uint32: 99342, 88 | EncodeIgnore: EncodeIgnore{ 89 | Export: "export field", 90 | }, 91 | EncodeNested: &EncodeNested{ 92 | Nested: "nested field", 93 | }, 94 | }, 95 | }, 96 | out: &EncodeStruct{ 97 | Ignored: "ignore", 98 | unexported: "unexported", 99 | EncodeEmbed: &EncodeEmbed{ 100 | EncodeIgnore: EncodeIgnore{ 101 | Export: "export field", 102 | }, 103 | }, 104 | }, 105 | }, 106 | { 107 | in: map[string]interface{}{ 108 | "integers": []interface{}{int64(1), int64(2), int64(3), int64(4)}, 109 | "tables": []interface{}{ 110 | map[string]interface{}{"description": "I am a TOML table"}, 111 | map[string]interface{}{"name": "Another TOML table"}, 112 | }, 113 | }, 114 | out: &map[string]interface{}{}, 115 | }, 116 | } 117 | 118 | func TestMarshal(t *testing.T) { 119 | for i, test := range marshalTests { 120 | b, err := toml.Marshal(test.in) 121 | 122 | if test.print && err == nil { 123 | t.Errorf("\n# %d: marshaled TOML document:\n%s# EOF\n", i, string(b)) 124 | } 125 | 126 | if test.err != nil { 127 | if !reflect.DeepEqual(test.err, err) { 128 | t.Errorf("#%d: error got %s\n, want %s", i, err, test.err) 129 | } 130 | continue 131 | } 132 | 133 | if err != nil { 134 | t.Errorf("#%d: got error: %s", i, err) 135 | continue 136 | } 137 | 138 | err = toml.Unmarshal(b, test.out) 139 | if err != nil { 140 | t.Errorf("#%d: unmarshal error: %s\ntext:\n%s\nfrom: %+v", i, err, string(b), test.in) 141 | continue 142 | } 143 | 144 | got := reflect.ValueOf(test.out).Elem().Interface() 145 | if !reflect.DeepEqual(test.in, got) { 146 | t.Errorf("#%d:\ngot %+v,\nwant %+v\n,\ntext:\n%s", i, got, test.in, string(b)) 147 | continue 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // ParseError describes errors raised in parsing phase. 8 | type ParseError struct { 9 | Line int // 1-based 10 | Pos int // 0-based, relative to beginning of input 11 | Err error 12 | } 13 | 14 | func (e *ParseError) Error() string { 15 | return fmt.Sprintf("toml: line %d, pos %d: %s", e.Line, e.Pos, e.Err.Error()) 16 | } 17 | -------------------------------------------------------------------------------- /example_unmarshal_test.go: -------------------------------------------------------------------------------- 1 | package toml_test 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/kezhuw/toml" 8 | ) 9 | 10 | func ExampleUnmarshal_integer() { 11 | data := []byte(`key = 12345`) 12 | var out struct{ Key int } 13 | 14 | err := toml.Unmarshal(data, &out) 15 | if err != nil { 16 | panic(err) 17 | } 18 | 19 | fmt.Println(out.Key) 20 | // Output: 12345 21 | } 22 | 23 | func ExampleUnmarshal_float() { 24 | data := []byte(`key = 3.14`) 25 | var out struct{ Key float64 } 26 | 27 | err := toml.Unmarshal(data, &out) 28 | if err != nil { 29 | panic(err) 30 | } 31 | 32 | fmt.Println(out.Key) 33 | // Output: 3.14 34 | } 35 | 36 | func ExampleUnmarshal_boolean() { 37 | data := []byte(`key = true`) 38 | var out struct{ Key bool } 39 | 40 | err := toml.Unmarshal(data, &out) 41 | if err != nil { 42 | panic(err) 43 | } 44 | 45 | fmt.Println(out.Key) 46 | // Output: true 47 | } 48 | 49 | func ExampleUnmarshal_string() { 50 | data := []byte(`key = "value"`) 51 | var out struct{ Key string } 52 | 53 | err := toml.Unmarshal(data, &out) 54 | if err != nil { 55 | panic(err) 56 | } 57 | 58 | fmt.Println(out.Key) 59 | // Output: value 60 | } 61 | 62 | func ExampleUnmarshal_datetimeNative() { 63 | data := []byte(`key = 2016-01-07T15:30:30.123456789Z`) 64 | var out struct{ Key time.Time } 65 | 66 | err := toml.Unmarshal(data, &out) 67 | if err != nil { 68 | panic(err) 69 | } 70 | 71 | fmt.Println(out.Key.Format(time.RFC3339Nano)) 72 | // Output: 2016-01-07T15:30:30.123456789Z 73 | } 74 | 75 | func ExampleUnmarshal_datetimeTextUnmarshaler() { 76 | data := []byte(`key = "2016-01-07T15:30:30.123456789Z"`) 77 | var out struct{ Key time.Time } 78 | 79 | err := toml.Unmarshal(data, &out) 80 | if err != nil { 81 | panic(err) 82 | } 83 | 84 | fmt.Println(out.Key.Format(time.RFC3339Nano)) 85 | // Output: 2016-01-07T15:30:30.123456789Z 86 | } 87 | 88 | func ExampleUnmarshal_array() { 89 | data := []byte(`key = [1, 2, 3,4]`) 90 | var out struct{ Key []int } 91 | 92 | err := toml.Unmarshal(data, &out) 93 | if err != nil { 94 | panic(err) 95 | } 96 | 97 | fmt.Println(out.Key) 98 | // Output: [1 2 3 4] 99 | } 100 | 101 | func ExampleUnmarshal_table() { 102 | data := []byte(`[key] 103 | name = "name" 104 | value = "value"`) 105 | var out struct { 106 | Key struct { 107 | Name string 108 | Value string 109 | } 110 | } 111 | 112 | err := toml.Unmarshal(data, &out) 113 | if err != nil { 114 | panic(err) 115 | } 116 | 117 | fmt.Printf("key.name = %q\n", out.Key.Name) 118 | fmt.Printf("key.value = %q\n", out.Key.Value) 119 | // Output: 120 | // key.name = "name" 121 | // key.value = "value" 122 | } 123 | 124 | func ExampleUnmarshal_inlineTable() { 125 | data := []byte(`key = { name = "name", value = "value" }`) 126 | var out struct { 127 | Key struct { 128 | Name string 129 | Value string 130 | } 131 | } 132 | 133 | err := toml.Unmarshal(data, &out) 134 | if err != nil { 135 | panic(err) 136 | } 137 | 138 | fmt.Printf("key.name = %q\n", out.Key.Name) 139 | fmt.Printf("key.value = %q\n", out.Key.Value) 140 | // Output: 141 | // key.name = "name" 142 | // key.value = "value" 143 | } 144 | 145 | func ExampleUnmarshal_tableArray() { 146 | data := []byte(` 147 | [[array]] 148 | description = "Table In Array" 149 | `) 150 | var out struct { 151 | Array []struct{ Description string } 152 | } 153 | 154 | err := toml.Unmarshal(data, &out) 155 | if err != nil { 156 | panic(err) 157 | } 158 | 159 | fmt.Println(out.Array[0].Description) 160 | // Output: Table In Array 161 | } 162 | 163 | func ExampleUnmarshal_interface() { 164 | data := []byte(`key = [1, 2, 3, 4,]`) 165 | var out struct{ Key interface{} } 166 | 167 | err := toml.Unmarshal(data, &out) 168 | if err != nil { 169 | panic(err) 170 | } 171 | 172 | fmt.Println(out.Key) 173 | // Output: [1 2 3 4] 174 | } 175 | 176 | func ExampleUnmarshal_tagName() { 177 | data := []byte(`KKKK = "value"`) 178 | var out struct { 179 | Key string `toml:"KKKK"` 180 | } 181 | 182 | err := toml.Unmarshal(data, &out) 183 | if err != nil { 184 | panic(err) 185 | } 186 | 187 | fmt.Println(out.Key) 188 | // Output: value 189 | } 190 | 191 | func ExampleUnmarshal_tagIgnore() { 192 | data := []byte(`key = "value"`) 193 | var out struct { 194 | Key string `toml:"-"` 195 | } 196 | 197 | err := toml.Unmarshal(data, &out) 198 | if err != nil { 199 | panic(err) 200 | } 201 | 202 | fmt.Println(out.Key) 203 | // Output: 204 | } 205 | 206 | func ExampleUnmarshal_tagString() { 207 | data := []byte(`key = "12345"`) 208 | var out struct { 209 | Key int `toml:",string"` 210 | } 211 | 212 | err := toml.Unmarshal(data, &out) 213 | if err != nil { 214 | panic(err) 215 | } 216 | 217 | fmt.Println(out.Key) 218 | // Output: 12345 219 | } 220 | 221 | func ExampleUnmarshal_tagOmitempty() { 222 | data := []byte(``) 223 | var out struct { 224 | Key string `toml:",omitempty"` 225 | } 226 | out.Key = "Not empty, for now." 227 | 228 | err := toml.Unmarshal(data, &out) 229 | if err != nil { 230 | panic(err) 231 | } 232 | 233 | fmt.Println(out.Key) 234 | // Output: 235 | } 236 | -------------------------------------------------------------------------------- /example_unmarshal_text_test.go: -------------------------------------------------------------------------------- 1 | package toml_test 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/kezhuw/toml" 8 | ) 9 | 10 | type duration time.Duration 11 | 12 | func (d *duration) UnmarshalText(b []byte) error { 13 | v, err := time.ParseDuration(string(b)) 14 | if err != nil { 15 | return err 16 | } 17 | *d = duration(v) 18 | return nil 19 | } 20 | 21 | func ExampleUnmarshal_textUnmarshaler() { 22 | data := []byte(`timeout = "300ms"`) 23 | var out struct{ Timeout duration } 24 | 25 | err := toml.Unmarshal(data, &out) 26 | if err != nil { 27 | panic(err) 28 | } 29 | 30 | fmt.Println(time.Duration(out.Timeout)) 31 | // Output: 300ms 32 | } 33 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kezhuw/toml 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /internal/types/array.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "time" 4 | 5 | func (v *Array) Interface() []interface{} { 6 | a := make([]interface{}, len(v.Elems)) 7 | for i, value := range v.Elems { 8 | switch value := value.(type) { 9 | case Boolean: 10 | a[i] = bool(value) 11 | case Integer: 12 | a[i] = int64(value) 13 | case Float: 14 | a[i] = float64(value) 15 | case String: 16 | a[i] = string(value) 17 | case Datetime: 18 | a[i] = time.Time(value) 19 | case *Array: 20 | a[i] = value.Interface() 21 | case *Table: 22 | a[i] = value.Interface() 23 | } 24 | } 25 | return a 26 | } 27 | -------------------------------------------------------------------------------- /internal/types/table.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "time" 4 | 5 | func (t *Table) Interface() map[string]interface{} { 6 | m := make(map[string]interface{}, len(t.Elems)) 7 | for key, value := range t.Elems { 8 | switch value := value.(type) { 9 | case Boolean: 10 | m[key] = bool(value) 11 | case Integer: 12 | m[key] = int64(value) 13 | case Float: 14 | m[key] = float64(value) 15 | case String: 16 | m[key] = string(value) 17 | case Datetime: 18 | m[key] = time.Time(value) 19 | case *Array: 20 | m[key] = value.Interface() 21 | case *Table: 22 | m[key] = value.Interface() 23 | } 24 | } 25 | return m 26 | } 27 | -------------------------------------------------------------------------------- /internal/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type Value interface { 8 | Type() string 9 | TOMLValue() 10 | } 11 | 12 | type Environment interface { 13 | Value 14 | TomlEnvironment() 15 | } 16 | 17 | type Array struct { 18 | Closed bool 19 | Elems []Value 20 | } 21 | 22 | type Table struct { 23 | Implicit bool 24 | Elems map[string]Value 25 | } 26 | 27 | type String string 28 | 29 | type Integer int64 30 | 31 | type Float float64 32 | 33 | type Boolean bool 34 | 35 | type Datetime time.Time 36 | 37 | func (t *Table) Type() string { return "table" } 38 | func (a *Array) Type() string { return "array" } 39 | func (s String) Type() string { return "string" } 40 | func (i Integer) Type() string { return "integer" } 41 | func (f Float) Type() string { return "float" } 42 | func (b Boolean) Type() string { return "boolean" } 43 | func (d Datetime) Type() string { return "datetime" } 44 | 45 | func (a *Array) TOMLValue() {} 46 | func (t *Table) TOMLValue() {} 47 | func (s String) TOMLValue() {} 48 | func (i Integer) TOMLValue() {} 49 | func (f Float) TOMLValue() {} 50 | func (b Boolean) TOMLValue() {} 51 | func (d Datetime) TOMLValue() {} 52 | 53 | func (a *Array) TomlEnvironment() {} 54 | func (t *Table) TomlEnvironment() {} 55 | -------------------------------------------------------------------------------- /key.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | ) 7 | 8 | func normalizeKey(key string) string { 9 | for _, r := range key { 10 | if !isBareKeyChar(r) { 11 | return strconv.Quote(key) 12 | } 13 | } 14 | return key 15 | } 16 | 17 | func combineKeyPath(path, key string) string { 18 | key = normalizeKey(key) 19 | if path == "" { 20 | return key 21 | } 22 | return path + "." + key 23 | } 24 | 25 | func combineIndexPath(path string, i int) string { 26 | return fmt.Sprintf("%s[%d]", path, i) 27 | } 28 | -------------------------------------------------------------------------------- /parse.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "strconv" 7 | "strings" 8 | "time" 9 | "unicode/utf8" 10 | 11 | "github.com/kezhuw/toml/internal/types" 12 | ) 13 | 14 | const ( 15 | eof = 0 16 | ) 17 | 18 | type scanner func(*parser) scanner 19 | 20 | type environment struct { 21 | env types.Environment 22 | path string 23 | } 24 | 25 | type parser struct { 26 | mark int 27 | 28 | pos int 29 | line int 30 | input string 31 | backups []int 32 | 33 | root *types.Table 34 | envs []environment 35 | keys []string 36 | 37 | names []string // table name parsing 38 | 39 | str strParser 40 | num numParser 41 | 42 | scanners []scanner 43 | 44 | err error 45 | } 46 | 47 | type numParser struct { 48 | sign string 49 | e string 50 | esign string 51 | integers []string 52 | fractions []string 53 | exponents []string 54 | } 55 | 56 | func (p *numParser) reset() { 57 | p.sign = "" 58 | p.e = "" 59 | p.esign = "" 60 | p.integers = p.integers[:0] 61 | p.fractions = p.fractions[:0] 62 | p.exponents = p.exponents[:0] 63 | } 64 | 65 | func (p *numParser) pushInteger(part string) { 66 | p.integers = append(p.integers, part) 67 | } 68 | 69 | func (p *numParser) pushFraction(part string) { 70 | p.fractions = append(p.fractions, part) 71 | } 72 | 73 | func (p *numParser) pushExponent(part string) { 74 | p.exponents = append(p.exponents, part) 75 | } 76 | 77 | func (p *numParser) join(sep string) string { 78 | s := p.sign + strings.Join(p.integers, sep) 79 | if len(p.fractions) != 0 { 80 | s += "." + strings.Join(p.fractions, sep) 81 | } 82 | if p.e != "" { 83 | s += p.e + p.esign + strings.Join(p.exponents, sep) 84 | } 85 | return s 86 | } 87 | 88 | func (p *numParser) Float() (float64, error) { 89 | defer p.reset() 90 | s := p.join("") 91 | return strconv.ParseFloat(s, 64) 92 | } 93 | 94 | func (p *numParser) Integer() (int64, error) { 95 | defer p.reset() 96 | s := strings.Join(p.integers, "") 97 | if s != "0" && s[0] == '0' { 98 | return 0, fmt.Errorf("leading zero in integer %q", s) 99 | } 100 | s = p.sign + s 101 | return strconv.ParseInt(s, 10, 64) 102 | } 103 | 104 | type strParser struct { 105 | parts []string 106 | } 107 | 108 | func (s *strParser) reset() { 109 | s.parts = s.parts[:0] 110 | } 111 | 112 | func (s *strParser) push(str string) { 113 | if str != "" { 114 | s.parts = append(s.parts, str) 115 | } 116 | } 117 | 118 | func (s *strParser) join() string { 119 | defer s.reset() 120 | return strings.Join(s.parts, "") 121 | } 122 | 123 | func (p *parser) pushScanner(s scanner) { 124 | p.scanners = append(p.scanners, s) 125 | } 126 | 127 | func (p *parser) seqScanner(seqs ...scanner) scanner { 128 | if len(seqs) == 0 { 129 | panic("toml: scanner sequences can't be empty") 130 | } 131 | for i := len(seqs) - 1; i >= 0; i-- { 132 | p.scanners = append(p.scanners, seqs[i]) 133 | } 134 | return p.popScanner() 135 | } 136 | 137 | func (p *parser) popScanner() (s scanner) { 138 | i := len(p.scanners) - 1 139 | p.scanners, s = p.scanners[:i], p.scanners[i] 140 | return s 141 | } 142 | 143 | func (p *parser) record(offset int) { 144 | p.mark = p.pos + offset 145 | } 146 | 147 | func (p *parser) slice(offset int) string { 148 | s := p.input[p.mark : p.pos+offset] 149 | p.mark = -1 150 | return s 151 | } 152 | 153 | func (p *parser) stepN(n int) { 154 | p.pos += n 155 | p.backups = append(p.backups, n) 156 | } 157 | 158 | func (p *parser) readRune() (r rune, n int) { 159 | r, n = utf8.DecodeRuneInString(p.input[p.pos:]) 160 | if r == utf8.RuneError { 161 | if n == 1 { 162 | panic(p.errorf("invalid utf8 rune %#.4x", p.input[p.pos:])) 163 | } 164 | if n == 0 { 165 | r = eof 166 | } 167 | } 168 | p.stepN(n) 169 | return r, n 170 | } 171 | 172 | func (p *parser) peekRune() (rune, int) { 173 | r, n := p.readRune() 174 | p.unread() 175 | return r, n 176 | } 177 | 178 | func (p *parser) readByte() rune { 179 | if p.pos >= len(p.input) { 180 | p.stepN(0) 181 | return eof 182 | } 183 | r := rune(p.input[p.pos]) 184 | p.stepN(1) 185 | return r 186 | } 187 | 188 | func (p *parser) peekByte() rune { 189 | if p.pos >= len(p.input) { 190 | return eof 191 | } 192 | return rune(p.input[p.pos]) 193 | } 194 | 195 | func (p *parser) tryReadByte(r rune) bool { 196 | if p.peekByte() == r { 197 | p.stepN(1) 198 | return true 199 | } 200 | return false 201 | } 202 | 203 | func (p *parser) tryReadPrefix(str string) bool { 204 | if strings.HasPrefix(p.input[p.pos:], str) { 205 | p.stepN(len(str)) 206 | return true 207 | } 208 | return false 209 | } 210 | 211 | func (p *parser) tryReadNewline() bool { 212 | r := p.readByte() 213 | if p.skipNewline(r) { 214 | return true 215 | } 216 | p.unread() 217 | return false 218 | } 219 | 220 | func (p *parser) unread() { 221 | i := len(p.backups) - 1 222 | rd := p.backups[i] 223 | p.backups = p.backups[:i] 224 | p.pos -= rd 225 | } 226 | 227 | func (p *parser) clearBackups() { 228 | p.backups = p.backups[:0] 229 | } 230 | 231 | func (p *parser) pushTableKey(key string) scanner { 232 | env, path := p.topEnv() 233 | if value, ok := env.(*types.Table).Elems[key]; ok { 234 | return p.errorScanner("table %s has key %s defined as %s", path, normalizeKey(key), value.Type()) 235 | } 236 | p.keys = append(p.keys, key) 237 | return p.popScanner() 238 | } 239 | 240 | func (p *parser) topTableKey() string { 241 | return p.keys[len(p.keys)-1] 242 | } 243 | 244 | func (p *parser) popTableKey() (key string) { 245 | i := len(p.keys) - 1 246 | p.keys, key = p.keys[0:i], p.keys[i] 247 | return key 248 | } 249 | 250 | func (p *parser) setValue(value types.Value) scanner { 251 | env, path := p.topEnv() 252 | switch env := env.(type) { 253 | case *types.Array: 254 | if len(env.Elems) != 0 { 255 | if first := env.Elems[0]; first.Type() != value.Type() { 256 | return p.errorScanner("array %s expects element type %s, but got %s", path, first.Type(), value.Type()) 257 | } 258 | } 259 | env.Elems = append(env.Elems, value) 260 | case *types.Table: 261 | key := p.popTableKey() 262 | env.Elems[key] = value 263 | } 264 | return p.popScanner() 265 | } 266 | 267 | func (p *parser) resetEnv(env types.Environment, path string) { 268 | p.envs = p.envs[:1] 269 | p.envs[0] = environment{env, path} 270 | } 271 | 272 | func (p *parser) pushEnv(new types.Environment) { 273 | env, path := p.topEnv() 274 | switch env := env.(type) { 275 | case *types.Table: 276 | path = combineKeyPath(path, p.topTableKey()) 277 | case *types.Array: 278 | path = combineIndexPath(path, len(env.Elems)) 279 | } 280 | p.envs = append(p.envs, environment{new, path}) 281 | } 282 | 283 | func (p *parser) popEnv() (env types.Environment, path string) { 284 | i := len(p.envs) - 1 285 | environment := p.envs[i] 286 | p.envs = p.envs[:i] 287 | return environment.env, environment.path 288 | } 289 | 290 | func (p *parser) topEnv() (types.Environment, string) { 291 | env := p.envs[len(p.envs)-1] 292 | return env.env, env.path 293 | } 294 | 295 | func scanByte(r rune) scanner { 296 | return func(p *parser) scanner { 297 | if r1 := p.readByte(); r1 != r { 298 | return p.expectRune(r) 299 | } 300 | return p.popScanner() 301 | } 302 | } 303 | 304 | func scanDigit(p *parser) scanner { 305 | r := p.readByte() 306 | if !isDigit(r) { 307 | return p.expectStr("digit") 308 | } 309 | return p.popScanner() 310 | } 311 | 312 | func scanConsumeByte(pred func(r rune) bool) scanner { 313 | var s scanner 314 | s = func(p *parser) scanner { 315 | if r := p.readByte(); !pred(r) { 316 | p.unread() 317 | return p.popScanner() 318 | } 319 | return s 320 | } 321 | return s 322 | } 323 | 324 | var scanColon = scanByte(':') 325 | var scanHash = scanByte('-') 326 | 327 | func scanRecord(offset int) scanner { 328 | return func(p *parser) scanner { 329 | p.record(offset) 330 | return p.popScanner() 331 | } 332 | } 333 | 334 | var scanRecord0 = scanRecord(0) 335 | 336 | func scanReturnString(p *parser, s string) scanner { 337 | p.str.push(s) 338 | return p.popScanner() 339 | } 340 | 341 | func scanUnicodeRune(p *parser, n int) scanner { 342 | p.record(0) 343 | for i := 0; i < n; i++ { 344 | if r := p.readByte(); !isHex(r) { 345 | return p.expectStr("hexadecimal digit") 346 | } 347 | } 348 | s := p.slice(0) 349 | codepoint, err := strconv.ParseUint(s, 16, 64) 350 | if err != nil { 351 | return p.setError(err) 352 | } 353 | r := rune(codepoint) 354 | if !utf8.ValidRune(r) { 355 | return p.errorScanner("%s is not a valid utf8 rune", s) 356 | } 357 | return scanReturnString(p, string(r)) 358 | } 359 | 360 | func scanEscapedRune(p *parser) scanner { 361 | r := p.readByte() 362 | switch r { 363 | case 'b': 364 | return scanReturnString(p, "\b") 365 | case 't': 366 | return scanReturnString(p, "\t") 367 | case 'n': 368 | return scanReturnString(p, "\n") 369 | case 'f': 370 | return scanReturnString(p, "\f") 371 | case 'r': 372 | return scanReturnString(p, "\r") 373 | case '"': 374 | return scanReturnString(p, "\"") 375 | case '\\': 376 | return scanReturnString(p, "\\") 377 | case 'u': 378 | return scanUnicodeRune(p, 4) 379 | case 'U': 380 | return scanUnicodeRune(p, 8) 381 | default: 382 | return p.expectStr("escaped sequence") 383 | } 384 | } 385 | 386 | func scanLiteral(p *parser) scanner { 387 | r, _ := p.readRune() 388 | switch r { 389 | case '\'': 390 | p.str.push(p.slice(-1)) 391 | return p.popScanner() 392 | case '\r', '\n': 393 | return p.errorScanner("newline is not allowed in oneline string") 394 | case eof: 395 | return p.errorScanner("string without ending") 396 | default: 397 | return scanLiteral 398 | } 399 | } 400 | 401 | func scanMultiLineLiteral(p *parser) scanner { 402 | r, _ := p.readRune() 403 | switch r { 404 | case eof: 405 | return p.errorScanner("no ending for multi-line literal string") 406 | case '\'': 407 | if p.tryReadPrefix(`''`) { 408 | p.str.push(p.slice(-3)) 409 | return p.popScanner() 410 | } 411 | fallthrough 412 | default: 413 | return scanMultiLineLiteral 414 | } 415 | } 416 | 417 | func scanMultiLineString(p *parser) scanner { 418 | r, _ := p.readRune() 419 | switch r { 420 | case '\\': 421 | p.str.push(p.slice(-1)) 422 | if p.tryReadNewline() { 423 | return p.seqScanner(scanConsumeByte(func(r rune) bool { return isSpace(r) || p.skipNewline(r) }), scanRecord0, scanMultiLineString) 424 | } 425 | return p.seqScanner(scanEscapedRune, scanRecord0, scanMultiLineString) 426 | case eof: 427 | return p.errorScanner("multi-line basic string without ending") 428 | case '"': 429 | if p.tryReadPrefix(`""`) { 430 | p.str.push(p.slice(-3)) 431 | return p.popScanner() 432 | } 433 | fallthrough 434 | default: 435 | return scanMultiLineString 436 | } 437 | } 438 | 439 | func scanComment(p *parser) scanner { 440 | r, _ := p.readRune() 441 | if r == eof || p.skipNewline(r) { 442 | return p.popScanner() 443 | } 444 | return scanComment 445 | } 446 | 447 | func scanArrayTableEnd(p *parser) scanner { 448 | i := len(p.names) - 1 449 | env, path := p.locateTable(p.names[:i]) 450 | if env == nil { 451 | return nil 452 | } 453 | 454 | env, path = p.createTableArray(env, path, p.names[i]) 455 | if env == nil { 456 | return nil 457 | } 458 | p.resetEnv(env, path) 459 | return scanTopEnd 460 | } 461 | 462 | func scanTableEnd(p *parser) scanner { 463 | i := len(p.names) - 1 464 | env, path := p.locateTable(p.names[:i]) 465 | if env == nil { 466 | return nil 467 | } 468 | 469 | env, path = p.createTable(env, path, p.names[i]) 470 | if env == nil { 471 | return nil 472 | } 473 | 474 | p.resetEnv(env, path) 475 | return scanTopEnd 476 | } 477 | 478 | func scanTableStart(p *parser) scanner { 479 | p.names = p.names[:0] 480 | if p.tryReadByte('[') { 481 | return p.seqScanner(scanTableNameStart, scanByte(']'), scanArrayTableEnd) 482 | } 483 | return p.seqScanner(scanTableNameStart, scanTableEnd) 484 | } 485 | 486 | func scanString(p *parser) scanner { 487 | r, _ := p.readRune() 488 | switch r { 489 | case '"': 490 | p.str.push(p.slice(-1)) 491 | return p.popScanner() 492 | case '\\': 493 | p.str.push(p.slice(-1)) 494 | return p.seqScanner(scanEscapedRune, scanRecord0, scanString) 495 | case '\r', '\n': 496 | return p.errorScanner("newline is not allowed in oneline string") 497 | case eof: 498 | return p.errorScanner("string without ending") 499 | default: 500 | return scanString 501 | } 502 | } 503 | 504 | func scanTableNameString(p *parser) scanner { 505 | s := p.str.join() 506 | p.appendTableName(s) 507 | return scanTableNameEnd 508 | } 509 | 510 | func scanTableNameStart(p *parser) scanner { 511 | r := p.readByte() 512 | switch { 513 | case isSpace(r): 514 | return scanTableNameStart 515 | case r == '.' || r == ']': 516 | return p.errorScanner("table name must be non-empty") 517 | case r == '"': 518 | return p.seqScanner(scanRecord0, scanString, scanTableNameString) 519 | case isBareKeyChar(r): 520 | p.record(-1) 521 | return scanTableNameInside 522 | default: 523 | return p.expectStr("table name") 524 | } 525 | } 526 | 527 | func (p *parser) appendTableName(name string) { 528 | p.names = append(p.names, name) 529 | } 530 | 531 | func scanTableNameInside(p *parser) scanner { 532 | r := p.readByte() 533 | switch { 534 | case isSpace(r): 535 | p.appendTableName(p.slice(-1)) 536 | return scanTableNameEnd 537 | case isBareKeyChar(r): 538 | return scanTableNameInside 539 | case r == '.': 540 | p.appendTableName(p.slice(-1)) 541 | return scanTableNameStart 542 | case r == ']': 543 | p.appendTableName(p.slice(-1)) 544 | return p.popScanner() 545 | default: 546 | return p.expectStr("bare character") 547 | } 548 | } 549 | 550 | func scanTableNameEnd(p *parser) scanner { 551 | r := p.readByte() 552 | switch { 553 | case isSpace(r): 554 | return scanTableNameEnd 555 | case r == '.': 556 | return scanTableNameStart 557 | case r == ']': 558 | return p.popScanner() 559 | default: 560 | return p.expectStr("'.' or ']'") 561 | } 562 | } 563 | 564 | func (p *parser) skipNewline(r rune) bool { 565 | switch r { 566 | case '\r': 567 | p.tryReadByte('\n') 568 | fallthrough 569 | case '\n': 570 | p.line++ 571 | return true 572 | } 573 | return false 574 | } 575 | 576 | func scanTop(p *parser) scanner { 577 | r := p.readByte() 578 | switch { 579 | case isSpace(r): 580 | return scanTop 581 | case p.skipNewline(r): 582 | return scanTop 583 | case r == '#': 584 | return p.seqScanner(scanComment, scanTop) 585 | case r == '[': 586 | return scanTableStart 587 | case r == eof: 588 | return nil 589 | default: 590 | p.unread() 591 | // Resumed after a whole a key/value pair was scanned. 592 | return p.seqScanner(scanTableField, scanTopEnd) 593 | } 594 | } 595 | 596 | func scanTopEnd(p *parser) scanner { 597 | r := p.readByte() 598 | switch { 599 | case r == eof: 600 | fallthrough 601 | case p.skipNewline(r): 602 | return scanTop 603 | case isSpace(r): 604 | return scanTopEnd 605 | case r == '#': 606 | return p.seqScanner(scanComment, scanTop) 607 | default: 608 | return p.expectStr("new line, comment or EOF") 609 | } 610 | } 611 | 612 | func scanInlineTableFieldEnd(p *parser) scanner { 613 | r := p.readByte() 614 | switch { 615 | case isSpace(r): 616 | return scanInlineTableFieldEnd 617 | case r == ',': 618 | return p.seqScanner(scanTableField, scanInlineTableFieldEnd) 619 | case r == '}': 620 | t, _ := p.popEnv() 621 | return p.setValue(t) 622 | default: 623 | return p.expectStr("inline table separator ',' or terminator '}'") 624 | } 625 | } 626 | 627 | func scanInlineTableStart(p *parser) scanner { 628 | r := p.readByte() 629 | switch { 630 | case p.skipNewline(r): 631 | return p.errorScanner("newlines are not allowed in inline table") 632 | case isSpace(r): 633 | return scanInlineTableStart 634 | case r == ',': 635 | return p.errorScanner("unexpected ',' in inline table") 636 | case r == '}': 637 | t := &types.Table{Elems: make(map[string]types.Value)} 638 | return p.setValue(t) 639 | default: 640 | p.unread() 641 | p.pushEnv(&types.Table{Elems: make(map[string]types.Value)}) 642 | return p.seqScanner(scanTableField, scanInlineTableFieldEnd) 643 | } 644 | } 645 | 646 | func scanFloatFraction(p *parser) scanner { 647 | r := p.readByte() 648 | switch { 649 | case isDigit(r): 650 | return scanFloatFraction 651 | case r == '_': 652 | p.num.pushFraction(p.slice(-1)) 653 | return p.seqScanner(scanRecord0, scanDigit, scanFloatFraction) 654 | case r == '.': 655 | return p.errorScanner("decimal point already read") 656 | case r == 'e' || r == 'E': 657 | p.num.e = string(r) 658 | p.num.pushFraction(p.slice(-1)) 659 | return scanFloatExponentSign 660 | default: 661 | p.unread() 662 | p.num.pushFraction(p.slice(0)) 663 | return setFloatValue(p) 664 | } 665 | } 666 | 667 | func scanFloatExponent(p *parser) scanner { 668 | r := p.readByte() 669 | switch { 670 | case isDigit(r): 671 | return scanFloatExponent 672 | case r == '_': 673 | p.num.pushExponent(p.slice(-1)) 674 | return p.seqScanner(scanRecord0, scanDigit, scanFloatExponent) 675 | default: 676 | p.unread() 677 | p.num.pushExponent(p.slice(0)) 678 | return setFloatValue(p) 679 | } 680 | } 681 | 682 | func scanFloatExponentSign(p *parser) scanner { 683 | switch r := p.readByte(); r { 684 | case '+', '-': 685 | p.num.esign = string(r) 686 | default: 687 | p.unread() 688 | } 689 | return p.seqScanner(scanRecord0, scanDigit, scanFloatExponent) 690 | } 691 | 692 | func scanNumber(p *parser) scanner { 693 | r := p.readByte() 694 | switch { 695 | case isDigit(r): 696 | return scanNumber 697 | case r == '_': 698 | p.num.pushInteger(p.slice(-1)) 699 | return p.seqScanner(scanRecord0, scanDigit, scanNumber) 700 | case r == '.': 701 | p.num.pushInteger(p.slice(-1)) 702 | return p.seqScanner(scanRecord0, scanDigit, scanFloatFraction) 703 | case r == 'e' || r == 'E': 704 | p.num.e = string(r) 705 | p.num.pushInteger(p.slice(-1)) 706 | return p.seqScanner(scanRecord0, scanDigit, scanFloatExponent) 707 | default: 708 | p.unread() 709 | p.num.pushInteger(p.slice(0)) 710 | return setIntegerValue(p) 711 | } 712 | } 713 | 714 | func scanNumberStart(p *parser) scanner { 715 | return p.seqScanner(scanRecord0, scanDigit, scanNumber) 716 | } 717 | 718 | func setFloatValue(p *parser) scanner { 719 | f, err := p.num.Float() 720 | if err != nil { 721 | return p.setError(err) 722 | } 723 | return p.setValue(types.Float(f)) 724 | } 725 | 726 | func setIntegerValue(p *parser) scanner { 727 | i, err := p.num.Integer() 728 | if err != nil { 729 | return p.setError(err) 730 | } 731 | return p.setValue(types.Integer(i)) 732 | } 733 | 734 | func setStringValue(p *parser) scanner { 735 | s := p.str.join() 736 | return p.setValue(types.String(s)) 737 | } 738 | 739 | func scanStringStart(p *parser) scanner { 740 | if p.tryReadPrefix(`""`) { 741 | p.tryReadNewline() 742 | return p.seqScanner(scanRecord0, scanMultiLineString, setStringValue) 743 | } 744 | return p.seqScanner(scanRecord0, scanString, setStringValue) 745 | } 746 | 747 | func scanLiteralStart(p *parser) scanner { 748 | if p.tryReadPrefix(`''`) { 749 | p.tryReadNewline() 750 | return p.seqScanner(scanRecord0, scanMultiLineLiteral, setStringValue) 751 | } 752 | return p.seqScanner(scanRecord0, scanLiteral, setStringValue) 753 | } 754 | 755 | func scanArrayValue(p *parser) scanner { 756 | r := p.readByte() 757 | switch { 758 | case isSpace(r) || p.skipNewline(r): 759 | return scanArrayValue 760 | case r == '#': 761 | return p.seqScanner(scanComment, scanArrayValue) 762 | case r == ',': 763 | return p.errorScanner("no array element before separator") 764 | case r == ']': 765 | p.unread() 766 | return p.popScanner() 767 | default: 768 | p.unread() 769 | return scanValue 770 | } 771 | } 772 | 773 | func scanArrayStart(p *parser) scanner { 774 | p.pushEnv(&types.Array{Closed: true, Elems: make([]types.Value, 0)}) 775 | return p.seqScanner(scanArrayValue, scanArrayEnd) 776 | } 777 | 778 | func scanArrayEnd(p *parser) scanner { 779 | r := p.readByte() 780 | switch { 781 | case isSpace(r): 782 | return scanArrayEnd 783 | case r == '#': 784 | return p.seqScanner(scanComment, scanArrayEnd) 785 | case r == ',': 786 | return p.seqScanner(scanArrayValue, scanArrayEnd) 787 | case r == ']': 788 | env, _ := p.popEnv() 789 | return p.setValue(env) 790 | default: 791 | return p.expectStr("',' or ']'") 792 | } 793 | } 794 | 795 | func scanValue(p *parser) scanner { 796 | r := p.readByte() 797 | switch { 798 | case r == '[': 799 | return scanArrayStart 800 | case r == '{': 801 | return scanInlineTableStart 802 | case r == 't': 803 | if !p.tryReadPrefix("rue") { 804 | return p.expectStr("true") 805 | } 806 | return p.setValue(types.Boolean(true)) 807 | case r == 'f': 808 | if !p.tryReadPrefix("alse") { 809 | return p.expectStr("false") 810 | } 811 | return p.setValue(types.Boolean(false)) 812 | case r == '"': 813 | return scanStringStart 814 | case r == '\'': 815 | return scanLiteralStart 816 | case r == '+' || r == '-': 817 | p.num.sign = string(r) 818 | return scanNumberStart 819 | case isDigit(r): 820 | p.record(-1) 821 | return scanNumberOrDate 822 | case isSpace(r): 823 | return scanValue 824 | default: 825 | return p.expectStr("value") 826 | } 827 | } 828 | 829 | func scanDateValue(p *parser, suffix string) scanner { 830 | s := p.slice(0) + suffix 831 | t, err := time.Parse(time.RFC3339Nano, s) 832 | if err != nil { 833 | return p.errorScanner(err.Error()) 834 | } 835 | return p.setValue(types.Datetime(t)) 836 | } 837 | 838 | func scanDateEnd(p *parser) scanner { 839 | return scanDateValue(p, "") 840 | } 841 | 842 | func scanDateTimeEnd(p *parser) scanner { 843 | r := p.readByte() 844 | switch r { 845 | case 'Z': 846 | return scanDateEnd(p) 847 | case '-': 848 | return p.seqScanner(scanDigit, scanDigit, scanColon, scanDigit, scanDigit, scanDateEnd) 849 | default: 850 | p.unread() 851 | return scanDateValue(p, "Z") 852 | } 853 | } 854 | 855 | func scanDateTimeFraction(p *parser) scanner { 856 | r := p.readByte() 857 | switch r { 858 | case '.': 859 | for isDigit(p.readByte()) { 860 | } 861 | fallthrough 862 | default: 863 | p.unread() 864 | return scanDateTimeEnd 865 | } 866 | } 867 | 868 | func scanDateTime(p *parser) scanner { 869 | r := p.readByte() 870 | switch r { 871 | case 'T': 872 | return p.seqScanner(scanDigit, scanDigit, scanColon, scanDigit, scanDigit, scanColon, scanDigit, scanDigit, scanDateTimeFraction) 873 | default: 874 | p.unread() 875 | return scanDateValue(p, "T00:00:00Z") 876 | } 877 | } 878 | 879 | func scanNumberOrDate(p *parser) scanner { 880 | r := p.readByte() 881 | switch { 882 | case r == '-': 883 | return p.seqScanner(scanDigit, scanDigit, scanHash, scanDigit, scanDigit, scanDateTime) 884 | case isDigit(r): 885 | return scanNumberOrDate 886 | default: 887 | p.unread() 888 | return scanNumber 889 | } 890 | } 891 | 892 | func scanFieldAssign(p *parser) scanner { 893 | r := p.readByte() 894 | switch { 895 | case isSpace(r): 896 | return scanFieldAssign 897 | case r == '=': 898 | return scanValue 899 | default: 900 | return p.expectRune('=') 901 | } 902 | } 903 | 904 | func scanBareKey(p *parser) scanner { 905 | r := p.readByte() 906 | switch { 907 | case isBareKeyChar(r): 908 | return scanBareKey 909 | case isSpace(r): 910 | key := p.slice(-1) 911 | p.pushScanner(scanFieldAssign) 912 | return p.pushTableKey(key) 913 | case r == '=': 914 | key := p.slice(-1) 915 | p.pushScanner(scanValue) 916 | return p.pushTableKey(key) 917 | default: 918 | return p.expectStr("bare character") 919 | } 920 | } 921 | 922 | func scanKeyEnd(p *parser) scanner { 923 | key := p.str.join() 924 | p.pushScanner(scanFieldAssign) 925 | return p.pushTableKey(key) 926 | } 927 | 928 | func scanTableField(p *parser) scanner { 929 | r := p.readByte() 930 | switch { 931 | case isSpace(r): 932 | return scanTableField 933 | case isBareKeyChar(r): 934 | p.record(-1) 935 | return scanBareKey 936 | case r == '=': 937 | return p.errorScanner("key must be non-empty") 938 | case r == '"': 939 | return p.seqScanner(scanRecord0, scanString, scanKeyEnd) 940 | case r == '\'': 941 | return p.seqScanner(scanRecord0, scanLiteral, scanKeyEnd) 942 | default: 943 | return p.expectStr("table field") 944 | } 945 | } 946 | 947 | type char rune 948 | 949 | func (c char) String() string { 950 | if c == eof { 951 | return "EOF" 952 | } 953 | return fmt.Sprintf("%q", rune(c)) 954 | } 955 | 956 | func (p *parser) expectRune(r rune) scanner { 957 | p.unread() 958 | got, _ := p.peekRune() 959 | p.err = &ParseError{p.line, p.pos, fmt.Errorf("expect %q, got %s", r, char(got))} 960 | return nil 961 | } 962 | 963 | func (p *parser) expectStr(str string) scanner { 964 | p.unread() 965 | got, _ := p.peekRune() 966 | p.err = &ParseError{p.line, p.pos, fmt.Errorf("expect %s, got %s", str, char(got))} 967 | return nil 968 | } 969 | 970 | func (p *parser) errorf(format string, args ...interface{}) error { 971 | return &ParseError{p.line, p.pos, fmt.Errorf(format, args...)} 972 | } 973 | 974 | func (p *parser) errorScanner(format string, args ...interface{}) scanner { 975 | p.err = p.errorf(format, args...) 976 | return nil 977 | } 978 | 979 | func (p *parser) setError(err error) scanner { 980 | p.err = &ParseError{p.line, p.pos, err} 981 | return nil 982 | } 983 | 984 | func (p *parser) locateTable(names []string) (t *types.Table, path string) { 985 | t = p.root 986 | for _, name := range names { 987 | path = combineKeyPath(path, name) 988 | switch v := t.Elems[name].(type) { 989 | case nil: 990 | ti := &types.Table{Implicit: true, Elems: make(map[string]types.Value)} 991 | t.Elems[name] = ti 992 | t = ti 993 | case *types.Table: 994 | t = v 995 | case *types.Array: 996 | if v.Closed { 997 | panic(p.errorf("%s was defined as array", path)) 998 | } 999 | i := len(v.Elems) - 1 1000 | t = v.Elems[i].(*types.Table) 1001 | path = combineIndexPath(path, i) 1002 | default: 1003 | panic(p.errorf("%s was defined as %s", path, v.Type())) 1004 | } 1005 | } 1006 | return t, path 1007 | } 1008 | 1009 | func (p *parser) createTable(env *types.Table, path string, name string) (*types.Table, string) { 1010 | path = combineKeyPath(path, name) 1011 | switch v := env.Elems[name].(type) { 1012 | case nil: 1013 | t := &types.Table{Elems: make(map[string]types.Value)} 1014 | env.Elems[name] = t 1015 | return t, path 1016 | case *types.Table: 1017 | if !v.Implicit { 1018 | panic(p.errorf("table %s was defined twice", path)) 1019 | } 1020 | v.Implicit = false 1021 | return v, path 1022 | default: 1023 | panic(p.errorf("%s was defined as %s", path, v.Type())) 1024 | } 1025 | } 1026 | 1027 | func (p *parser) createTableArray(env *types.Table, path string, name string) (*types.Table, string) { 1028 | path = combineKeyPath(path, name) 1029 | t := &types.Table{Elems: make(map[string]types.Value)} 1030 | switch v := env.Elems[name].(type) { 1031 | case nil: 1032 | env.Elems[name] = &types.Array{Elems: []types.Value{t}} 1033 | case *types.Array: 1034 | if v.Closed { 1035 | panic(p.errorf("%s was defined as array", path)) 1036 | } 1037 | v.Elems = append(v.Elems, t) 1038 | default: 1039 | panic(p.errorf("%s was defined as %s", path, v.Type())) 1040 | } 1041 | return t, path 1042 | } 1043 | 1044 | func (p *parser) errRecover(errp *error) { 1045 | if r := recover(); r != nil { 1046 | switch err := r.(type) { 1047 | default: 1048 | panic(r) 1049 | case runtime.Error: 1050 | panic(r) 1051 | case *ParseError: 1052 | *errp = err 1053 | case error: 1054 | *errp = &ParseError{p.line, p.pos, err} 1055 | } 1056 | } 1057 | } 1058 | 1059 | func (p *parser) parse() (err error) { 1060 | defer p.errRecover(&err) 1061 | scanner := scanTop 1062 | for scanner != nil { 1063 | p.clearBackups() 1064 | scanner = scanner(p) 1065 | } 1066 | return p.err 1067 | } 1068 | 1069 | func newParser(t *types.Table, s string) *parser { 1070 | return &parser{ 1071 | mark: -1, 1072 | line: 1, 1073 | input: s, 1074 | root: t, 1075 | envs: []environment{{t, ""}}, 1076 | } 1077 | } 1078 | 1079 | // Parse parses TOML document from data, and represents it in types.Table. 1080 | func parse(data []byte) (*types.Table, error) { 1081 | root := &types.Table{Elems: make(map[string]types.Value)} 1082 | p := newParser(root, string(data)) 1083 | err := p.parse() 1084 | if err != nil { 1085 | return nil, err 1086 | } 1087 | return root, nil 1088 | } 1089 | -------------------------------------------------------------------------------- /pred.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | func isDigit(r rune) bool { 4 | return '0' <= r && r <= '9' 5 | } 6 | 7 | func isHex(r rune) bool { 8 | switch { 9 | default: 10 | return false 11 | case 'A' <= r && r <= 'Z': 12 | case 'a' <= r && r <= 'z': 13 | case '0' <= r && r <= '9': 14 | } 15 | return true 16 | } 17 | 18 | func isSpace(r rune) bool { 19 | return r == ' ' || r == '\t' 20 | } 21 | 22 | func isBareKeyChar(r rune) bool { 23 | switch { 24 | default: 25 | return false 26 | case 'A' <= r && r <= 'Z': 27 | case 'a' <= r && r <= 'z': 28 | case '0' <= r && r <= '9': 29 | case r == '-' || r == '_': 30 | } 31 | return true 32 | } 33 | -------------------------------------------------------------------------------- /tags.go: -------------------------------------------------------------------------------- 1 | package toml 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | type tagOptions map[string]struct{} 8 | 9 | func (o tagOptions) Has(opt string) bool { 10 | _, ok := o[opt] 11 | return ok 12 | } 13 | 14 | func parseTag(tag string) (string, tagOptions) { 15 | splits := strings.Split(tag, ",") 16 | if len(splits) == 1 { 17 | return splits[0], nil 18 | } 19 | options := make(map[string]struct{}, len(splits)-1) 20 | for i := 1; i < len(splits); i++ { 21 | options[splits[i]] = struct{}{} 22 | } 23 | return splits[0], options 24 | } 25 | --------------------------------------------------------------------------------