├── .gitignore ├── .travis.yml ├── Makefile ├── README.md ├── decoder.go ├── decoder_test.go ├── go-libucl.h ├── libucl.go ├── object.go ├── object_test.go ├── parser.go └── parser_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant/ 2 | vendor/ 3 | /libucl.dll 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.2.1 5 | 6 | script: make test 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LIBUCL_NAME=libucl.a 2 | 3 | # If we're on Windows, we need to change some variables so things compile 4 | # properly. 5 | ifeq ($(OS), Windows_NT) 6 | LIBUCL_NAME=libucl.dll 7 | endif 8 | 9 | export CGO_CFLAGS CGO_LDFLAGS PATH 10 | 11 | all: libucl 12 | go test 13 | 14 | libucl: vendor/libucl/$(LIBUCL_NAME) 15 | 16 | vendor/libucl/libucl.a: vendor/libucl 17 | cd vendor/libucl && \ 18 | cmake cmake/ && \ 19 | make 20 | 21 | vendor/libucl/libucl.dll: vendor/libucl 22 | cd vendor/libucl && \ 23 | $(MAKE) -f Makefile.w32 && \ 24 | cp .obj/libucl.dll . && \ 25 | cp libucl.dll $(CURDIR) 26 | 27 | vendor/libucl: 28 | rm -rf vendor/libucl 29 | mkdir -p vendor/libucl 30 | git clone https://github.com/vstakhov/libucl.git vendor/libucl 31 | 32 | clean: 33 | rm -rf vendor 34 | 35 | .PHONY: all clean libucl test 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Libucl Library for Go 2 | 3 | go-libucl is a [libucl](https://github.com/vstakhov/libucl) library for 4 | [Go](http://golang.org). Rather than re-implement libucl in Go, this library 5 | uses cgo to bind directly to libucl. This allows the libucl project to be 6 | the central source of knowledge. This project works on Mac OS X, Linux, and 7 | Windows. 8 | 9 | **Warning:** This library is still under development and API compatibility 10 | is not guaranteed. Additionally, it is not feature complete yet, though 11 | it is certainly usable for real purposes (we do!). 12 | 13 | ## Installation 14 | 15 | Because we vendor the source of libucl, you can go ahead and get it directly. 16 | We'll keep up to date with libucl. The package name is `libucl`. 17 | 18 | ``` 19 | $ go get github.com/mitchellh/go-libucl 20 | ``` 21 | 22 | Documentation is available on GoDoc: http://godoc.org/github.com/mitchellh/go-libucl 23 | 24 | ### Compiling Libucl 25 | 26 | Libucl should compile easily and cleanly on POSIX systems. 27 | 28 | On Windows, msys should be used. msys-regex needs to be compiled. 29 | -------------------------------------------------------------------------------- /decoder.go: -------------------------------------------------------------------------------- 1 | package libucl 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | const tagName = "libucl" 11 | 12 | // Decode decodes a libucl object into a native Go structure. 13 | func (o *Object) Decode(v interface{}) error { 14 | return decode("", o, reflect.ValueOf(v).Elem()) 15 | } 16 | 17 | func decode(name string, o *Object, result reflect.Value) error { 18 | switch result.Kind() { 19 | case reflect.Bool: 20 | return decodeIntoBool(name, o, result) 21 | case reflect.Interface: 22 | // Interface is a bit weird. When we see an interface, we do 23 | // our best effort to determine the type, and put it into that. 24 | return decodeIntoInterface(name, o, result) 25 | case reflect.Int: 26 | return decodeIntoInt(name, o, result) 27 | case reflect.Map: 28 | return decodeIntoMap(name, o, result) 29 | case reflect.Ptr: 30 | return decodeIntoPtr(name, o, result) 31 | case reflect.Slice: 32 | return decodeIntoSlice(name, o, result) 33 | case reflect.String: 34 | return decodeIntoString(name, o, result) 35 | case reflect.Struct: 36 | return decodeIntoStruct(name, o, result) 37 | default: 38 | return fmt.Errorf("%s: unsupported type: %s", name, result.Kind()) 39 | } 40 | 41 | return nil 42 | } 43 | 44 | func decodeIntoBool(name string, o *Object, result reflect.Value) error { 45 | switch o.Type() { 46 | case ObjectTypeString: 47 | b, err := strconv.ParseBool(o.ToString()) 48 | if err == nil { 49 | result.SetBool(b) 50 | } else { 51 | return fmt.Errorf("cannot parse '%s' as bool: %s", name, err) 52 | } 53 | default: 54 | result.SetBool(o.ToBool()) 55 | } 56 | 57 | return nil 58 | } 59 | 60 | func decodeIntoInt(name string, o *Object, result reflect.Value) error { 61 | switch o.Type() { 62 | case ObjectTypeString: 63 | i, err := strconv.ParseInt(o.ToString(), 0, result.Type().Bits()) 64 | if err == nil { 65 | result.SetInt(i) 66 | } else { 67 | return fmt.Errorf("cannot parse '%s' as int: %s", name, err) 68 | } 69 | default: 70 | result.SetInt(o.ToInt()) 71 | } 72 | 73 | return nil 74 | } 75 | 76 | func decodeIntoInterface(name string, o *Object, result reflect.Value) error { 77 | var set reflect.Value 78 | redecode := true 79 | 80 | switch o.Type() { 81 | case ObjectTypeArray: 82 | redecode = false 83 | 84 | result := make([]interface{}, 0, int(o.Len())) 85 | 86 | iter := o.Iterate(true) 87 | defer iter.Close() 88 | for o := iter.Next(); o != nil; o = iter.Next() { 89 | raw := new(interface{}) 90 | err := decode(name, o, reflect.Indirect(reflect.ValueOf(raw))) 91 | o.Close() 92 | 93 | if err != nil { 94 | return err 95 | } 96 | 97 | result = append(result, *raw) 98 | } 99 | 100 | set = reflect.ValueOf(result) 101 | case ObjectTypeBoolean: 102 | set = reflect.Indirect(reflect.New(reflect.TypeOf(o.ToBool()))) 103 | case ObjectTypeInt: 104 | var result int 105 | set = reflect.Indirect(reflect.New(reflect.TypeOf(result))) 106 | case ObjectTypeObject: 107 | redecode = false 108 | 109 | result := make([]map[string]interface{}, 0, int(o.Len())) 110 | 111 | var err error 112 | outer := o.Iterate(false) 113 | defer outer.Close() 114 | for o := outer.Next(); o != nil; o = outer.Next() { 115 | m := make(map[string]interface{}) 116 | inner := o.Iterate(true) 117 | for o2 := inner.Next(); o2 != nil; o2 = inner.Next() { 118 | var raw interface{} 119 | err = decode(name, o2, reflect.Indirect(reflect.ValueOf(&raw))) 120 | o2.Close() 121 | if err != nil { 122 | break 123 | } 124 | 125 | m[o2.Key()] = raw 126 | } 127 | inner.Close() 128 | o.Close() 129 | 130 | if err != nil { 131 | return err 132 | } 133 | 134 | result = append(result, m) 135 | } 136 | 137 | set = reflect.ValueOf(result) 138 | case ObjectTypeString: 139 | set = reflect.Indirect(reflect.New(reflect.TypeOf(""))) 140 | default: 141 | return fmt.Errorf( 142 | "%s: unsupported type to interface: %s", name, o.Type()) 143 | } 144 | 145 | if redecode { 146 | if err := decode(name, o, set); err != nil { 147 | return err 148 | } 149 | } 150 | 151 | result.Set(set) 152 | return nil 153 | } 154 | 155 | func decodeIntoMap(name string, o *Object, result reflect.Value) error { 156 | if o.Type() != ObjectTypeObject { 157 | return fmt.Errorf("%s: not an object type, can't decode to map", name) 158 | } 159 | 160 | resultType := result.Type() 161 | resultElemType := resultType.Elem() 162 | resultKeyType := resultType.Key() 163 | if resultKeyType.Kind() != reflect.String { 164 | return fmt.Errorf("%s: map must have string keys", name) 165 | } 166 | 167 | // Make a map to store our result 168 | resultMap := result 169 | if result.IsNil() { 170 | resultMap = reflect.MakeMap( 171 | reflect.MapOf(resultKeyType, resultElemType)) 172 | } 173 | 174 | outerIter := o.Iterate(false) 175 | defer outerIter.Close() 176 | for outer := outerIter.Next(); outer != nil; outer = outerIter.Next() { 177 | iter := outer.Iterate(true) 178 | defer iter.Close() 179 | for elem := iter.Next(); elem != nil; elem = iter.Next() { 180 | fieldName := fmt.Sprintf("%s[%s]", name, elem.Key()) 181 | 182 | key := reflect.ValueOf(elem.Key()) 183 | 184 | // The value we have to be decode 185 | val := reflect.Indirect(reflect.New(resultElemType)) 186 | 187 | // If we have a pre-existing value in the map, use that 188 | oldVal := resultMap.MapIndex(key) 189 | if oldVal.IsValid() { 190 | val.Set(oldVal) 191 | } 192 | 193 | err := decode(fieldName, elem, val) 194 | elem.Close() 195 | if err != nil { 196 | return err 197 | } 198 | 199 | resultMap.SetMapIndex(key, val) 200 | } 201 | } 202 | 203 | // Set the final result 204 | result.Set(resultMap) 205 | 206 | return nil 207 | } 208 | 209 | func decodeIntoPtr(name string, o *Object, result reflect.Value) error { 210 | // Create an element of the concrete (non pointer) type and decode 211 | // into that. Then set the value of the pointer to this type. 212 | resultType := result.Type() 213 | resultElemType := resultType.Elem() 214 | val := reflect.New(resultElemType) 215 | if err := decode(name, o, reflect.Indirect(val)); err != nil { 216 | return err 217 | } 218 | 219 | result.Set(val) 220 | return nil 221 | } 222 | 223 | func decodeIntoSlice(name string, o *Object, result reflect.Value) error { 224 | // Create the slice 225 | resultType := result.Type() 226 | resultElemType := resultType.Elem() 227 | resultSliceType := reflect.SliceOf(resultElemType) 228 | resultSlice := reflect.MakeSlice( 229 | resultSliceType, 0, int(o.Len())) 230 | 231 | // Determine how we're doing this 232 | expand := true 233 | switch o.Type() { 234 | case ObjectTypeObject: 235 | expand = false 236 | default: 237 | // Array or anything else: we expand values and take it all 238 | } 239 | 240 | i := 0 241 | iter := o.Iterate(expand) 242 | defer iter.Close() 243 | for elem := iter.Next(); elem != nil; elem = iter.Next() { 244 | val := reflect.Indirect(reflect.New(resultElemType)) 245 | fieldName := fmt.Sprintf("%s[%d]", name, i) 246 | err := decode(fieldName, elem, val) 247 | elem.Close() 248 | if err != nil { 249 | return err 250 | } 251 | 252 | resultSlice = reflect.Append(resultSlice, val) 253 | 254 | i++ 255 | } 256 | 257 | result.Set(resultSlice) 258 | 259 | return nil 260 | } 261 | 262 | func decodeIntoString(name string, o *Object, result reflect.Value) error { 263 | objType := o.Type() 264 | switch objType { 265 | case ObjectTypeBoolean: 266 | result.SetString(strconv.FormatBool(o.ToBool())) 267 | case ObjectTypeString: 268 | result.SetString(o.ToString()) 269 | case ObjectTypeInt: 270 | result.SetString(strconv.FormatInt(o.ToInt(), 10)) 271 | default: 272 | return fmt.Errorf("%s: unsupported type to string: %s", name, objType) 273 | } 274 | 275 | return nil 276 | } 277 | 278 | func decodeIntoStruct(name string, o *Object, result reflect.Value) error { 279 | // This slice will keep track of all the structs we'll be decoding. 280 | // There can be more than one struct if there are embedded structs 281 | // that are squashed. 282 | structs := make([]reflect.Value, 1, 5) 283 | structs[0] = result 284 | 285 | // Compile the list of all the fields that we're going to be decoding 286 | // from all the structs. 287 | fields := make(map[*reflect.StructField]reflect.Value) 288 | for len(structs) > 0 { 289 | structVal := structs[0] 290 | structs = structs[1:] 291 | 292 | structType := structVal.Type() 293 | for i := 0; i < structType.NumField(); i++ { 294 | fieldType := structType.Field(i) 295 | 296 | if fieldType.Anonymous { 297 | fieldKind := fieldType.Type.Kind() 298 | if fieldKind != reflect.Struct { 299 | return fmt.Errorf( 300 | "%s: unsupported type to struct: %s", 301 | fieldType.Name, fieldKind) 302 | } 303 | 304 | // We have an embedded field. We "squash" the fields down 305 | // if specified in the tag. 306 | squash := false 307 | tagParts := strings.Split(fieldType.Tag.Get(tagName), ",") 308 | for _, tag := range tagParts[1:] { 309 | if tag == "squash" { 310 | squash = true 311 | break 312 | } 313 | } 314 | 315 | if squash { 316 | structs = append(structs, result.FieldByName(fieldType.Name)) 317 | continue 318 | } 319 | } 320 | 321 | // Normal struct field, store it away 322 | fields[&fieldType] = structVal.Field(i) 323 | } 324 | } 325 | 326 | usedKeys := make(map[string]struct{}) 327 | decodedFields := make([]string, 0, len(fields)) 328 | decodedFieldsVal := make([]reflect.Value, 0) 329 | unusedKeysVal := make([]reflect.Value, 0) 330 | for fieldType, field := range fields { 331 | if !field.IsValid() { 332 | // This should never happen 333 | panic("field is not valid") 334 | } 335 | 336 | // If we can't set the field, then it is unexported or something, 337 | // and we just continue onwards. 338 | if !field.CanSet() { 339 | continue 340 | } 341 | 342 | fieldName := fieldType.Name 343 | 344 | tagValue := fieldType.Tag.Get(tagName) 345 | tagParts := strings.SplitN(tagValue, ",", 2) 346 | if len(tagParts) >= 2 { 347 | switch tagParts[1] { 348 | case "decodedFields": 349 | decodedFieldsVal = append(decodedFieldsVal, field) 350 | continue 351 | case "key": 352 | field.SetString(o.Key()) 353 | continue 354 | case "object": 355 | // Increase the ref count 356 | o.Ref() 357 | 358 | // Sete the object 359 | field.Set(reflect.ValueOf(o)) 360 | continue 361 | case "unusedKeys": 362 | unusedKeysVal = append(unusedKeysVal, field) 363 | continue 364 | } 365 | } 366 | 367 | if tagParts[0] != "" { 368 | fieldName = tagParts[0] 369 | } 370 | 371 | elem := o.Get(fieldName) 372 | if elem == nil { 373 | // Do a slower search by iterating over each key and 374 | // doing case-insensitive search. 375 | iter := o.Iterate(true) 376 | for elem = iter.Next(); elem != nil; elem = iter.Next() { 377 | if strings.EqualFold(elem.Key(), fieldName) { 378 | break 379 | } 380 | 381 | elem.Close() 382 | } 383 | iter.Close() 384 | 385 | if elem == nil { 386 | // No key matching this field. 387 | continue 388 | } 389 | } 390 | 391 | // Track the used key 392 | usedKeys[elem.Key()] = struct{}{} 393 | 394 | // If the name is empty string, then we're at the root, and we 395 | // don't dot-join the fields. 396 | if name != "" { 397 | fieldName = fmt.Sprintf("%s.%s", name, fieldName) 398 | } 399 | 400 | var err error 401 | if field.Kind() == reflect.Slice { 402 | err = decode(fieldName, elem, field) 403 | } else { 404 | iter := elem.Iterate(false) 405 | for { 406 | obj := iter.Next() 407 | if obj == nil { 408 | break 409 | } 410 | 411 | err = decode(fieldName, obj, field) 412 | obj.Close() 413 | if err != nil { 414 | break 415 | } 416 | } 417 | iter.Close() 418 | } 419 | elem.Close() 420 | 421 | if err != nil { 422 | return err 423 | } 424 | 425 | decodedFields = append(decodedFields, fieldType.Name) 426 | } 427 | 428 | for _, v := range decodedFieldsVal { 429 | v.Set(reflect.ValueOf(decodedFields)) 430 | } 431 | 432 | // If we want to know what keys are unused, compile thta 433 | if len(unusedKeysVal) > 0 { 434 | unusedKeys := make([]string, 0, int(o.Len())-len(usedKeys)) 435 | 436 | iter := o.Iterate(true) 437 | defer iter.Close() 438 | for elem := iter.Next(); elem != nil; elem = iter.Next() { 439 | k := elem.Key() 440 | if _, ok := usedKeys[k]; !ok { 441 | unusedKeys = append(unusedKeys, k) 442 | } 443 | elem.Close() 444 | } 445 | 446 | if len(unusedKeys) == 0 { 447 | unusedKeys = nil 448 | } 449 | 450 | for _, v := range unusedKeysVal { 451 | v.Set(reflect.ValueOf(unusedKeys)) 452 | } 453 | } 454 | 455 | return nil 456 | } 457 | -------------------------------------------------------------------------------- /decoder_test.go: -------------------------------------------------------------------------------- 1 | package libucl 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestObjectDecode_basic(t *testing.T) { 9 | type Basic struct { 10 | Bool bool 11 | BoolStr string 12 | Str string 13 | Num int 14 | NumStr int 15 | } 16 | 17 | obj := testParseString(t, ` 18 | bool = true; str = bar; num = 7; numstr = "42"; 19 | boolstr = true; 20 | `) 21 | defer obj.Close() 22 | 23 | var result Basic 24 | if err := obj.Decode(&result); err != nil { 25 | t.Fatalf("err: %s", err) 26 | } 27 | 28 | expected := Basic{ 29 | Bool: true, 30 | BoolStr: "true", 31 | Str: "bar", 32 | Num: 7, 33 | NumStr: 42, 34 | } 35 | 36 | if !reflect.DeepEqual(result, expected) { 37 | t.Fatalf("bad: %#v", result) 38 | } 39 | } 40 | 41 | func TestObjectDecode_interface(t *testing.T) { 42 | obj := testParseString(t, ` 43 | foo { 44 | f1 = "foo"; 45 | f2 = [1, 2, 3]; 46 | f3 = ["foo", 2, 42]; 47 | f4 = true; 48 | } 49 | `) 50 | defer obj.Close() 51 | 52 | obj = obj.Get("foo") 53 | defer obj.Close() 54 | 55 | var result map[string]interface{} 56 | if err := obj.Decode(&result); err != nil { 57 | t.Fatalf("err: %s", err) 58 | } 59 | 60 | if len(result) != 4 { 61 | t.Fatalf("bad: %#v", result) 62 | } 63 | 64 | if result["f1"].(string) != "foo" { 65 | t.Fatalf("bad: %#v", result) 66 | } 67 | 68 | expected := []interface{}{1, 2, 3} 69 | if !reflect.DeepEqual(result["f2"], expected) { 70 | t.Fatalf("bad: %#v", result["f2"]) 71 | } 72 | 73 | expected = []interface{}{"foo", 2, 42} 74 | if !reflect.DeepEqual(result["f3"], expected) { 75 | t.Fatalf("bad: %#v", result["f3"]) 76 | } 77 | 78 | if result["f4"].(bool) != true { 79 | t.Fatalf("bad: %#v", result) 80 | } 81 | } 82 | 83 | func TestObjectDecode_map(t *testing.T) { 84 | obj := testParseString(t, "foo = bar; bar = 12;") 85 | defer obj.Close() 86 | 87 | var result map[string]string 88 | if err := obj.Decode(&result); err != nil { 89 | t.Fatalf("err: %s", err) 90 | } 91 | 92 | if result["foo"] != "bar" { 93 | t.Fatalf("bad: %#v", result["foo"]) 94 | } 95 | if result["bar"] != "12" { 96 | t.Fatalf("bad: %#v", result["bar"]) 97 | } 98 | } 99 | 100 | func TestObjectDecode_mapMulti(t *testing.T) { 101 | obj := testParseString(t, "foo { foo = bar; }; foo { bar = baz; };") 102 | defer obj.Close() 103 | 104 | inner := obj.Get("foo") 105 | defer inner.Close() 106 | 107 | var result map[string]string 108 | if err := inner.Decode(&result); err != nil { 109 | t.Fatalf("err: %s", err) 110 | } 111 | 112 | if result["foo"] != "bar" { 113 | t.Fatalf("bad: %#v", result["foo"]) 114 | } 115 | if result["bar"] != "baz" { 116 | t.Fatalf("bad: %#v", result["bar"]) 117 | } 118 | } 119 | 120 | func TestObjectDecode_mapNonNil(t *testing.T) { 121 | obj := testParseString(t, "foo = bar; bar = 12;") 122 | defer obj.Close() 123 | 124 | result := map[string]string{"hey": "hello"} 125 | if err := obj.Decode(&result); err != nil { 126 | t.Fatalf("err: %s", err) 127 | } 128 | 129 | if result["hey"] != "hello" { 130 | t.Fatalf("bad hey!") 131 | } 132 | if result["foo"] != "bar" { 133 | t.Fatalf("bad: %#v", result["foo"]) 134 | } 135 | if result["bar"] != "12" { 136 | t.Fatalf("bad: %#v", result["bar"]) 137 | } 138 | } 139 | 140 | func TestObjectDecode_mapNonObject(t *testing.T) { 141 | obj := testParseString(t, "foo = [bar];") 142 | defer obj.Close() 143 | 144 | obj = obj.Get("foo") 145 | defer obj.Close() 146 | 147 | var result map[string]string 148 | if err := obj.Decode(&result); err == nil { 149 | t.Fatal("should fail") 150 | } 151 | } 152 | 153 | func TestObjectDecode_mapObject(t *testing.T) { 154 | obj := testParseString(t, "foo = bar; bar { baz = \"what\" }") 155 | defer obj.Close() 156 | 157 | var result map[string]interface{} 158 | if err := obj.Decode(&result); err != nil { 159 | t.Fatalf("err: %s", err) 160 | } 161 | 162 | expected := map[string]interface{}{ 163 | "foo": "bar", 164 | "bar": []map[string]interface{}{ 165 | map[string]interface{}{ 166 | "baz": "what", 167 | }, 168 | }, 169 | } 170 | 171 | if !reflect.DeepEqual(result, expected) { 172 | t.Fatalf("bad: %#v", result) 173 | } 174 | } 175 | 176 | func TestObjectDecode_mapObjectMultiple(t *testing.T) { 177 | obj := testParseString(t, ` 178 | foo = bar 179 | bar { baz = "what" } 180 | bar { port = 3000 } 181 | `) 182 | defer obj.Close() 183 | 184 | var result map[string]interface{} 185 | if err := obj.Decode(&result); err != nil { 186 | t.Fatalf("err: %s", err) 187 | } 188 | 189 | expected := map[string]interface{}{ 190 | "foo": "bar", 191 | "bar": []map[string]interface{}{ 192 | map[string]interface{}{ 193 | "baz": "what", 194 | }, 195 | map[string]interface{}{ 196 | "port": 3000, 197 | }, 198 | }, 199 | } 200 | 201 | if !reflect.DeepEqual(result, expected) { 202 | t.Fatalf("bad: %#v", result) 203 | } 204 | } 205 | 206 | func TestObjectDecode_mapReuseVal(t *testing.T) { 207 | type Struct struct { 208 | Foo string 209 | Bar string 210 | } 211 | 212 | type Result struct { 213 | Struct map[string]Struct 214 | } 215 | 216 | obj := testParseString(t, ` 217 | struct "foo" { foo = "bar"; }; 218 | struct "foo" { bar = "baz"; }; 219 | `) 220 | defer obj.Close() 221 | 222 | var result Result 223 | if err := obj.Decode(&result); err != nil { 224 | t.Fatalf("err: %s", err) 225 | } 226 | 227 | expected := Result{ 228 | Struct: map[string]Struct{ 229 | "foo": Struct{ 230 | Foo: "bar", 231 | Bar: "baz", 232 | }, 233 | }, 234 | } 235 | 236 | if !reflect.DeepEqual(result, expected) { 237 | t.Fatalf("bad: %#v", result) 238 | } 239 | } 240 | 241 | func TestObjectDecode_nestedStruct(t *testing.T) { 242 | type Nested struct { 243 | Foo string 244 | } 245 | 246 | var result struct { 247 | Value Nested 248 | } 249 | 250 | obj := testParseString(t, `value { foo = "bar"; }`) 251 | defer obj.Close() 252 | 253 | if err := obj.Decode(&result); err != nil { 254 | t.Fatalf("err: %s", err) 255 | } 256 | 257 | if result.Value.Foo != "bar" { 258 | t.Fatalf("bad: %#v", result.Value.Foo) 259 | } 260 | } 261 | 262 | func TestObjectDecode_nestedStructRepeated(t *testing.T) { 263 | type Nested struct { 264 | Foo string 265 | Bar string 266 | } 267 | 268 | var result struct { 269 | Value Nested 270 | } 271 | 272 | obj := testParseString(t, `value { foo = "bar"; }; value { bar = "baz" };`) 273 | defer obj.Close() 274 | 275 | if err := obj.Decode(&result); err != nil { 276 | t.Fatalf("err: %s", err) 277 | } 278 | 279 | if result.Value.Foo != "bar" { 280 | t.Fatalf("bad: %#v", result.Value.Foo) 281 | } 282 | if result.Value.Bar != "baz" { 283 | t.Fatalf("bad: %#v", result.Value.Bar) 284 | } 285 | } 286 | 287 | func TestObjectDecode_slice(t *testing.T) { 288 | obj := testParseString(t, "foo = [foo, bar, 12];") 289 | defer obj.Close() 290 | 291 | obj = obj.Get("foo") 292 | defer obj.Close() 293 | 294 | var result []string 295 | if err := obj.Decode(&result); err != nil { 296 | t.Fatalf("err: %s", err) 297 | } 298 | 299 | expected := []string{"foo", "bar", "12"} 300 | if !reflect.DeepEqual(expected, result) { 301 | t.Fatalf("bad: %#v", result) 302 | } 303 | } 304 | 305 | func TestObjectDecode_sliceRepeatedKey(t *testing.T) { 306 | obj := testParseString(t, "foo = foo; foo = bar;") 307 | defer obj.Close() 308 | 309 | obj = obj.Get("foo") 310 | defer obj.Close() 311 | 312 | var result []string 313 | if err := obj.Decode(&result); err != nil { 314 | t.Fatalf("err: %s", err) 315 | } 316 | 317 | expected := []string{"foo", "bar"} 318 | if !reflect.DeepEqual(expected, result) { 319 | t.Fatalf("bad: %#v", result) 320 | } 321 | } 322 | 323 | func TestObjectDecode_sliceSingle(t *testing.T) { 324 | obj := testParseString(t, "foo = bar;") 325 | defer obj.Close() 326 | 327 | obj = obj.Get("foo") 328 | defer obj.Close() 329 | 330 | var result []string 331 | if err := obj.Decode(&result); err != nil { 332 | t.Fatalf("err: %s", err) 333 | } 334 | 335 | expected := []string{"bar"} 336 | if !reflect.DeepEqual(expected, result) { 337 | t.Fatalf("bad: %#v", result) 338 | } 339 | } 340 | 341 | func TestObjectDecode_struct(t *testing.T) { 342 | var result struct { 343 | Foo []string 344 | Bar string 345 | } 346 | 347 | obj := testParseString(t, "foo = [foo, bar, 12]; bar = baz;") 348 | defer obj.Close() 349 | 350 | if err := obj.Decode(&result); err != nil { 351 | t.Fatalf("err: %s", err) 352 | } 353 | 354 | expected := []string{"foo", "bar", "12"} 355 | if !reflect.DeepEqual(expected, result.Foo) { 356 | t.Fatalf("bad: %#v", result.Foo) 357 | } 358 | if result.Bar != "baz" { 359 | t.Fatalf("bad: %#v", result.Bar) 360 | } 361 | } 362 | 363 | func TestObjectDecode_structKeys(t *testing.T) { 364 | type Struct struct { 365 | Foo []string 366 | Bar string 367 | Baz string 368 | Keys []string `libucl:",decodedFields"` 369 | } 370 | 371 | var result Struct 372 | 373 | obj := testParseString(t, "foo = [foo, bar, 12]; bar = baz;") 374 | defer obj.Close() 375 | 376 | if err := obj.Decode(&result); err != nil { 377 | t.Fatalf("err: %s", err) 378 | } 379 | 380 | expected := Struct{ 381 | Foo: []string{"foo", "bar", "12"}, 382 | Bar: "baz", 383 | Keys: []string{"Foo", "Bar"}, 384 | } 385 | if !reflect.DeepEqual(expected, result) { 386 | t.Fatalf("bad: %#v", result) 387 | } 388 | } 389 | 390 | func TestObjectDecode_mapStructNamed(t *testing.T) { 391 | type Nested struct { 392 | Name string `libucl:",key"` 393 | Foo string 394 | } 395 | 396 | var result struct { 397 | Value map[string]Nested 398 | } 399 | 400 | obj := testParseString(t, `value "foo" { foo = "bar"; };`) 401 | defer obj.Close() 402 | 403 | if err := obj.Decode(&result); err != nil { 404 | t.Fatalf("err: %s", err) 405 | } 406 | 407 | expected := map[string]Nested{ 408 | "foo": Nested{ 409 | Name: "foo", 410 | Foo: "bar", 411 | }, 412 | } 413 | 414 | if !reflect.DeepEqual(result.Value, expected) { 415 | t.Fatalf("bad: %#v", result.Value) 416 | } 417 | } 418 | 419 | func TestObjectDecode_mapStructObject(t *testing.T) { 420 | type Nested struct { 421 | Foo string 422 | Object *Object `libucl:",object"` 423 | } 424 | 425 | var result struct { 426 | Value map[string]Nested 427 | } 428 | 429 | obj := testParseString(t, `value "foo" { foo = "bar"; };`) 430 | defer obj.Close() 431 | 432 | valueObj := obj.Get("value") 433 | defer valueObj.Close() 434 | 435 | fooObj := valueObj.Get("foo") 436 | defer fooObj.Close() 437 | 438 | if err := obj.Decode(&result); err != nil { 439 | t.Fatalf("err: %s", err) 440 | } 441 | defer result.Value["foo"].Object.Close() 442 | 443 | expected := map[string]Nested{ 444 | "foo": Nested{ 445 | Foo: "bar", 446 | Object: fooObj, 447 | }, 448 | } 449 | 450 | if !reflect.DeepEqual(result.Value, expected) { 451 | t.Fatalf("bad: %#v", result.Value) 452 | } 453 | } 454 | 455 | func TestObjectDecode_structArray(t *testing.T) { 456 | type Nested struct { 457 | Foo string 458 | } 459 | 460 | var result struct { 461 | Value []*Nested 462 | } 463 | 464 | obj := testParseString(t, `value { foo = "bar"; }; value { foo = "baz"; }`) 465 | defer obj.Close() 466 | 467 | if err := obj.Decode(&result); err != nil { 468 | t.Fatalf("err: %s", err) 469 | } 470 | 471 | if len(result.Value) != 2 { 472 | t.Fatalf("bad: %#v", result.Value) 473 | } 474 | if result.Value[0].Foo != "bar" { 475 | t.Fatalf("bad: %#v", result.Value[0].Foo) 476 | } 477 | if result.Value[1].Foo != "baz" { 478 | t.Fatalf("bad: %#v", result.Value[1].Foo) 479 | } 480 | } 481 | 482 | func TestObjectDecode_structSquash(t *testing.T) { 483 | type Foo struct { 484 | Baz string 485 | } 486 | 487 | var result struct { 488 | Bar string 489 | Foo `libucl:",squash"` 490 | } 491 | 492 | obj := testParseString(t, "baz = what; bar = baz;") 493 | defer obj.Close() 494 | 495 | if err := obj.Decode(&result); err != nil { 496 | t.Fatalf("err: %s", err) 497 | } 498 | 499 | if result.Bar != "baz" { 500 | t.Fatalf("bad: %#v", result.Bar) 501 | } 502 | if result.Baz != "what" { 503 | t.Fatalf("bad: %#v", result.Baz) 504 | } 505 | } 506 | 507 | func TestObjectDecode_structUnusedKeys(t *testing.T) { 508 | type Struct struct { 509 | Bar string 510 | Keys []string `libucl:",unusedKeys"` 511 | } 512 | 513 | var result Struct 514 | 515 | obj := testParseString(t, "foo = [bar]; bar = baz; baz = what;") 516 | defer obj.Close() 517 | 518 | if err := obj.Decode(&result); err != nil { 519 | t.Fatalf("err: %s", err) 520 | } 521 | 522 | expected := Struct{ 523 | Bar: "baz", 524 | Keys: []string{"foo", "baz"}, 525 | } 526 | if !reflect.DeepEqual(expected, result) { 527 | t.Fatalf("bad: %#v", result) 528 | } 529 | } 530 | -------------------------------------------------------------------------------- /go-libucl.h: -------------------------------------------------------------------------------- 1 | #ifndef _GOLIBUCL_H_INCLUDED 2 | #define _GOLIBUCL_H_INCLUDED 3 | 4 | #include 5 | #include 6 | 7 | static inline char *_go_uchar_to_char(const unsigned char *c) { 8 | return (char *)c; 9 | } 10 | 11 | //------------------------------------------------------------------- 12 | // Helpers: Macros 13 | //------------------------------------------------------------------- 14 | 15 | // This is declared in parser.go and invokes the Go function callback for 16 | // a specific macro (specified by the ID). 17 | extern bool go_macro_call(int, char *data, int); 18 | 19 | // Indirection that actually calls the Go macro handler. 20 | static inline bool _go_macro_handler(const unsigned char *data, size_t len, void* ud) { 21 | return go_macro_call((int)ud, (char*)data, (int)len); 22 | } 23 | 24 | // Returns the ucl_macro_handler that we have, since we can't get this 25 | // type from cgo. 26 | static inline ucl_macro_handler _go_macro_handler_func() { 27 | return &_go_macro_handler; 28 | } 29 | 30 | // This just converts an int to a void*, because Go doesn't let us do that 31 | // and we use an int as the user data for registering macros. 32 | static inline void *_go_macro_index(int idx) { 33 | return (void *)idx; 34 | } 35 | 36 | #endif /* _GOLIBUCL_H_INCLUDED */ 37 | -------------------------------------------------------------------------------- /libucl.go: -------------------------------------------------------------------------------- 1 | package libucl 2 | 3 | // #cgo CFLAGS: -Ivendor/libucl/include -Wno-int-to-void-pointer-cast 4 | // #cgo LDFLAGS: -Lvendor/libucl -lucl 5 | import "C" 6 | -------------------------------------------------------------------------------- /object.go: -------------------------------------------------------------------------------- 1 | package libucl 2 | 3 | import "unsafe" 4 | 5 | // #include "go-libucl.h" 6 | import "C" 7 | 8 | // Object represents a single object within a configuration. 9 | type Object struct { 10 | object *C.ucl_object_t 11 | } 12 | 13 | // ObjectIter is an interator for objects. 14 | type ObjectIter struct { 15 | expand bool 16 | object *C.ucl_object_t 17 | iter C.ucl_object_iter_t 18 | } 19 | 20 | // ObjectType is an enum of the type that an Object represents. 21 | type ObjectType int 22 | 23 | const ( 24 | ObjectTypeObject ObjectType = iota 25 | ObjectTypeArray 26 | ObjectTypeInt 27 | ObjectTypeFloat 28 | ObjectTypeString 29 | ObjectTypeBoolean 30 | ObjectTypeTime 31 | ObjectTypeUserData 32 | ObjectTypeNull 33 | ) 34 | 35 | // Emitter is a type of built-in emitter that can be used to convert 36 | // an object to another config format. 37 | type Emitter int 38 | 39 | const ( 40 | EmitJSON Emitter = iota 41 | EmitJSONCompact 42 | EmitConfig 43 | EmitYAML 44 | ) 45 | 46 | // Free the memory associated with the object. This must be called when 47 | // you're done using it. 48 | func (o *Object) Close() error { 49 | C.ucl_object_unref(o.object) 50 | return nil 51 | } 52 | 53 | // Emit converts this object to another format and returns it. 54 | func (o *Object) Emit(t Emitter) (string, error) { 55 | result := C.ucl_object_emit(o.object, uint32(t)) 56 | if result == nil { 57 | return "", nil 58 | } 59 | 60 | return C.GoString(C._go_uchar_to_char(result)), nil 61 | } 62 | 63 | // Delete removes the given key from the object. The key will automatically 64 | // be dereferenced once when this is called. 65 | func (o *Object) Delete(key string) { 66 | ckey := C.CString(key) 67 | defer C.free(unsafe.Pointer(ckey)) 68 | 69 | C.ucl_object_delete_key(o.object, ckey) 70 | } 71 | 72 | func (o *Object) Get(key string) *Object { 73 | ckey := C.CString(key) 74 | defer C.free(unsafe.Pointer(ckey)) 75 | 76 | obj := C.ucl_object_find_keyl(o.object, ckey, C.size_t(len(key))) 77 | if obj == nil { 78 | return nil 79 | } 80 | 81 | result := &Object{object: obj} 82 | result.Ref() 83 | return result 84 | } 85 | 86 | // Iterate over the objects in this object. 87 | // 88 | // The iterator must be closed when it is finished. 89 | // 90 | // The iterator does not need to be fully consumed. 91 | func (o *Object) Iterate(expand bool) *ObjectIter { 92 | // Increase the ref count 93 | C.ucl_object_ref(o.object) 94 | 95 | return &ObjectIter{ 96 | expand: expand, 97 | object: o.object, 98 | iter: nil, 99 | } 100 | } 101 | 102 | // Returns the key of this value/object as a string, or the empty 103 | // string if the object doesn't have a key. 104 | func (o *Object) Key() string { 105 | return C.GoString(C.ucl_object_key(o.object)) 106 | } 107 | 108 | // Len returns the length of the object, or how many elements are part 109 | // of this object. 110 | // 111 | // For objects, this is the number of key/value pairs. 112 | // For arrays, this is the number of elements. 113 | func (o *Object) Len() uint { 114 | // This is weird. If the object is an object and it has a "next", 115 | // then it is actually an array of objects, and to get the count 116 | // we actually need to iterate and count. 117 | if o.Type() == ObjectTypeObject && o.object.next != nil { 118 | iter := o.Iterate(false) 119 | defer iter.Close() 120 | 121 | var count uint = 0 122 | for obj := iter.Next(); obj != nil; obj = iter.Next() { 123 | obj.Close() 124 | count += 1 125 | } 126 | 127 | return count 128 | } 129 | 130 | return uint(o.object.len) 131 | } 132 | 133 | // Increments the ref count associated with this. You have to call 134 | // close an additional time to free the memory. 135 | func (o *Object) Ref() error { 136 | C.ucl_object_ref(o.object) 137 | return nil 138 | } 139 | 140 | // Returns the type that this object represents. 141 | func (o *Object) Type() ObjectType { 142 | return ObjectType(C.ucl_object_type(o.object)) 143 | } 144 | 145 | //------------------------------------------------------------------------ 146 | // Conversion Functions 147 | //------------------------------------------------------------------------ 148 | 149 | func (o *Object) ToBool() bool { 150 | return bool(C.ucl_object_toboolean(o.object)) 151 | } 152 | 153 | func (o *Object) ToInt() int64 { 154 | return int64(C.ucl_object_toint(o.object)) 155 | } 156 | 157 | func (o *Object) ToFloat() float64 { 158 | return float64(C.ucl_object_todouble(o.object)) 159 | } 160 | 161 | func (o *Object) ToString() string { 162 | return C.GoString(C.ucl_object_tostring(o.object)) 163 | } 164 | 165 | func (o *ObjectIter) Close() { 166 | C.ucl_object_unref(o.object) 167 | } 168 | 169 | func (o *ObjectIter) Next() *Object { 170 | obj := C.ucl_iterate_object(o.object, &o.iter, C._Bool(o.expand)) 171 | if obj == nil { 172 | return nil 173 | } 174 | 175 | // Increase the ref count so we have to free it 176 | C.ucl_object_ref(obj) 177 | 178 | return &Object{object: obj} 179 | } 180 | -------------------------------------------------------------------------------- /object_test.go: -------------------------------------------------------------------------------- 1 | package libucl 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestObjectEmit(t *testing.T) { 9 | obj := testParseString(t, "foo = bar; bar = baz;") 10 | defer obj.Close() 11 | 12 | result, err := obj.Emit(EmitJSON) 13 | if err != nil { 14 | t.Fatalf("err: %s", err) 15 | } 16 | 17 | expected := "{\n \"foo\": \"bar\",\n \"bar\": \"baz\"\n}" 18 | if result != expected { 19 | t.Fatalf("bad: %#v", result) 20 | } 21 | } 22 | 23 | func TestObjectEmit_EmitConfig(t *testing.T) { 24 | obj := testParseString(t, "foo = bar; bar = baz;") 25 | defer obj.Close() 26 | 27 | result, err := obj.Emit(EmitConfig) 28 | if err != nil { 29 | t.Fatalf("err: %s", err) 30 | } 31 | 32 | expected := "foo = \"bar\";\nbar = \"baz\";\n" 33 | if result != expected { 34 | t.Fatalf("bad: %#v", result) 35 | } 36 | } 37 | 38 | func TestObjectDelete(t *testing.T) { 39 | obj := testParseString(t, "bar = baz;") 40 | defer obj.Close() 41 | 42 | v := obj.Get("bar") 43 | if v == nil { 44 | t.Fatal("should find") 45 | } 46 | v.Close() 47 | 48 | obj.Delete("bar") 49 | v = obj.Get("bar") 50 | if v != nil { 51 | v.Close() 52 | t.Fatalf("should not find") 53 | } 54 | } 55 | 56 | func TestObjectDelete_unknown(t *testing.T) { 57 | obj := testParseString(t, "bar = baz;") 58 | defer obj.Close() 59 | 60 | obj.Delete("foo") 61 | } 62 | 63 | func TestObjectGet(t *testing.T) { 64 | obj := testParseString(t, "foo = bar; bar = baz;") 65 | defer obj.Close() 66 | 67 | v := obj.Get("bar") 68 | defer v.Close() 69 | if v == nil { 70 | t.Fatal("should find") 71 | } 72 | 73 | if v.Key() != "bar" { 74 | t.Fatalf("bad: %#v", v.Key()) 75 | } 76 | if v.ToString() != "baz" { 77 | t.Fatalf("bad: %#v", v.ToString()) 78 | } 79 | } 80 | 81 | func TestObjectLen_array(t *testing.T) { 82 | obj := testParseString(t, "foo = [foo, bar, baz];") 83 | defer obj.Close() 84 | 85 | v := obj.Get("foo") 86 | defer v.Close() 87 | if v == nil { 88 | t.Fatal("should find") 89 | } 90 | 91 | if v.Len() != 3 { 92 | t.Fatalf("bad: %#v", v.Len()) 93 | } 94 | } 95 | 96 | func TestObjectLen_object(t *testing.T) { 97 | obj := testParseString(t, `bundle "foo" {}; bundle "bar" {};`) 98 | defer obj.Close() 99 | 100 | v := obj.Get("bundle") 101 | defer v.Close() 102 | if v == nil { 103 | t.Fatal("should find") 104 | } 105 | 106 | if v.Type() != ObjectTypeObject { 107 | t.Fatalf("bad: %#v", v.Type()) 108 | } 109 | if v.Len() != 2 { 110 | t.Fatalf("bad: %#v", v.Len()) 111 | } 112 | } 113 | 114 | func TestObjectIterate(t *testing.T) { 115 | obj := testParseString(t, "foo = bar; bar = baz;") 116 | defer obj.Close() 117 | 118 | iter := obj.Iterate(true) 119 | defer iter.Close() 120 | 121 | result := make([]string, 0, 10) 122 | for elem := iter.Next(); elem != nil; elem = iter.Next() { 123 | defer elem.Close() 124 | result = append(result, elem.Key()) 125 | result = append(result, elem.ToString()) 126 | } 127 | 128 | expected := []string{"foo", "bar", "bar", "baz"} 129 | if !reflect.DeepEqual(expected, result) { 130 | t.Fatalf("bad: %#v", result) 131 | } 132 | } 133 | 134 | func TestObjectIterate_array(t *testing.T) { 135 | obj := testParseString(t, "foo = [foo, bar, baz];") 136 | defer obj.Close() 137 | 138 | obj = obj.Get("foo") 139 | if obj == nil { 140 | t.Fatal("should have object") 141 | } 142 | 143 | iter := obj.Iterate(true) 144 | defer iter.Close() 145 | 146 | result := make([]string, 0, 10) 147 | for elem := iter.Next(); elem != nil; elem = iter.Next() { 148 | defer elem.Close() 149 | result = append(result, elem.ToString()) 150 | } 151 | 152 | expected := []string{"foo", "bar", "baz"} 153 | if !reflect.DeepEqual(expected, result) { 154 | t.Fatalf("bad: %#v", result) 155 | } 156 | } 157 | 158 | func TestObjectToBool(t *testing.T) { 159 | obj := testParseString(t, "foo = true; bar = false;") 160 | defer obj.Close() 161 | 162 | v := obj.Get("bar") 163 | defer v.Close() 164 | if v == nil { 165 | t.Fatal("should find") 166 | } 167 | if v.ToBool() { 168 | t.Fatalf("bad: %#v", v.ToBool()) 169 | } 170 | } 171 | 172 | func TestObjectToFloat_oneThousand(t *testing.T) { 173 | obj := testParseString(t, "foo = 1000; ") 174 | defer obj.Close() 175 | 176 | v := obj.Get("foo") 177 | defer v.Close() 178 | if v == nil { 179 | t.Fatal("should find") 180 | } 181 | if v.ToFloat() != 1000 { 182 | t.Fatalf("bad: %#v", v.ToFloat()) 183 | } 184 | } 185 | 186 | func TestObjectToFloat_oneThousandth(t *testing.T) { 187 | obj := testParseString(t, "foo = 0.001; ") 188 | defer obj.Close() 189 | 190 | v := obj.Get("foo") 191 | defer v.Close() 192 | if v == nil { 193 | t.Fatal("should find") 194 | } 195 | if v.ToFloat() != 0.001 { 196 | t.Fatalf("bad: %#v", v.ToFloat()) 197 | } 198 | } 199 | 200 | func TestObjectToFloat_oneThird(t *testing.T) { 201 | /* from a c progam that prints 1/3: 202 | * 0.3333333333333333148296162562473909929394721984863281 203 | */ 204 | obj := testParseString(t, "foo = 0.3333333333333333148296162562473909929394721984863281; ") 205 | defer obj.Close() 206 | 207 | var g float64 = 1.0 / 3.0 208 | 209 | v := obj.Get("foo") 210 | defer v.Close() 211 | if v == nil { 212 | t.Fatal("should find") 213 | } 214 | if v.ToFloat() != g { 215 | t.Fatalf("bad: %#v, expected: %v", v.ToFloat(), g) 216 | } 217 | } 218 | 219 | func TestObjectToFloat_negativeOneThird(t *testing.T) { 220 | /* from a c progam that prints 1/3: 221 | * -0.3333333333333333148296162562473909929394721984863281 222 | */ 223 | obj := testParseString(t, "foo = -0.3333333333333333148296162562473909929394721984863281; ") 224 | defer obj.Close() 225 | 226 | var g float64 = -1.0 / 3.0 227 | 228 | v := obj.Get("foo") 229 | defer v.Close() 230 | if v == nil { 231 | t.Fatal("should find") 232 | } 233 | if v.ToFloat() != g { 234 | t.Fatalf("bad: %#v, expected: %v", v.ToFloat(), g) 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /parser.go: -------------------------------------------------------------------------------- 1 | package libucl 2 | 3 | import ( 4 | "errors" 5 | "sync" 6 | "unsafe" 7 | ) 8 | 9 | // #include "go-libucl.h" 10 | import "C" 11 | 12 | // MacroFunc is the callback type for macros. 13 | type MacroFunc func(string) 14 | 15 | // ParserFlag are flags that can be used to initialize a parser. 16 | // 17 | // ParserKeyLowercase will lowercase all keys. 18 | // 19 | // ParserKeyZeroCopy will attempt to do a zero-copy parse if possible. 20 | type ParserFlag int 21 | 22 | const ( 23 | ParserKeyLowercase ParserFlag = C.UCL_PARSER_KEY_LOWERCASE 24 | ParserZeroCopy = C.UCL_PARSER_ZEROCOPY 25 | ParserNoTime = C.UCL_PARSER_NO_TIME 26 | ) 27 | 28 | // Keeps track of all the macros internally 29 | var macros map[int]MacroFunc = nil 30 | var macrosIdx int = 0 31 | var macrosLock sync.Mutex 32 | 33 | // Parser is responsible for parsing libucl data. 34 | type Parser struct { 35 | macros []int 36 | parser *C.struct_ucl_parser 37 | } 38 | 39 | // ParseString parses a string and returns the top-level object. 40 | func ParseString(data string) (*Object, error) { 41 | p := NewParser(0) 42 | defer p.Close() 43 | if err := p.AddString(data); err != nil { 44 | return nil, err 45 | } 46 | 47 | return p.Object(), nil 48 | } 49 | 50 | // NewParser returns a parser 51 | func NewParser(flags ParserFlag) *Parser { 52 | return &Parser{ 53 | parser: C.ucl_parser_new(C.int(flags)), 54 | } 55 | } 56 | 57 | // AddString adds a string data to parse. 58 | func (p *Parser) AddString(data string) error { 59 | cs := C.CString(data) 60 | defer C.free(unsafe.Pointer(cs)) 61 | 62 | result := C.ucl_parser_add_string(p.parser, cs, C.size_t(len(data))) 63 | if !result { 64 | errstr := C.ucl_parser_get_error(p.parser) 65 | return errors.New(C.GoString(errstr)) 66 | } 67 | return nil 68 | } 69 | 70 | // AddFile adds a file to parse. 71 | func (p *Parser) AddFile(path string) error { 72 | cs := C.CString(path) 73 | defer C.free(unsafe.Pointer(cs)) 74 | 75 | result := C.ucl_parser_add_file(p.parser, cs) 76 | if !result { 77 | errstr := C.ucl_parser_get_error(p.parser) 78 | return errors.New(C.GoString(errstr)) 79 | } 80 | return nil 81 | } 82 | 83 | // Closes the parser. Once it is closed it can no longer be used. You 84 | // should always close the parser once you're done with it to clean up 85 | // any unused memory. 86 | func (p *Parser) Close() { 87 | C.ucl_parser_free(p.parser) 88 | 89 | if len(p.macros) > 0 { 90 | macrosLock.Lock() 91 | defer macrosLock.Unlock() 92 | for _, idx := range p.macros { 93 | delete(macros, idx) 94 | } 95 | } 96 | } 97 | 98 | // Retrieves the root-level object for a configuration. 99 | func (p *Parser) Object() *Object { 100 | obj := C.ucl_parser_get_object(p.parser) 101 | if obj == nil { 102 | return nil 103 | } 104 | 105 | return &Object{object: obj} 106 | } 107 | 108 | // RegisterMacro registers a macro that is called from the configuration. 109 | func (p *Parser) RegisterMacro(name string, f MacroFunc) { 110 | // Register it globally 111 | macrosLock.Lock() 112 | if macros == nil { 113 | macros = make(map[int]MacroFunc) 114 | } 115 | for macros[macrosIdx] != nil { 116 | macrosIdx++ 117 | } 118 | idx := macrosIdx 119 | macros[idx] = f 120 | macrosIdx++ 121 | macrosLock.Unlock() 122 | 123 | // Register the index with our parser so we can free it 124 | p.macros = append(p.macros, idx) 125 | 126 | cname := C.CString(name) 127 | defer C.free(unsafe.Pointer(cname)) 128 | 129 | C.ucl_parser_register_macro( 130 | p.parser, 131 | cname, 132 | C._go_macro_handler_func(), 133 | C._go_macro_index(C.int(idx))) 134 | } 135 | 136 | //export go_macro_call 137 | func go_macro_call(id C.int, data *C.char, n C.int) C.bool { 138 | macrosLock.Lock() 139 | f := macros[int(id)] 140 | macrosLock.Unlock() 141 | 142 | // Macro not found, return error 143 | if f == nil { 144 | return false 145 | } 146 | 147 | // Macro found, call it! 148 | f(C.GoStringN(data, n)) 149 | return true 150 | } 151 | -------------------------------------------------------------------------------- /parser_test.go: -------------------------------------------------------------------------------- 1 | package libucl 2 | 3 | import ( 4 | "io/ioutil" 5 | "testing" 6 | ) 7 | 8 | func testParseString(t *testing.T, data string) *Object { 9 | obj, err := ParseString(data) 10 | if err != nil { 11 | t.Fatalf("err: %s", err) 12 | } 13 | 14 | return obj 15 | } 16 | 17 | func TestParser(t *testing.T) { 18 | p := NewParser(0) 19 | defer p.Close() 20 | 21 | if err := p.AddString(`foo = bar;`); err != nil { 22 | t.Fatalf("err: %s", err) 23 | } 24 | 25 | obj := p.Object() 26 | if obj == nil { 27 | t.Fatal("obj should not be nil") 28 | } 29 | defer obj.Close() 30 | 31 | if obj.Type() != ObjectTypeObject { 32 | t.Fatalf("bad: %#v", obj.Type()) 33 | } 34 | 35 | value := obj.Get("foo") 36 | if value == nil { 37 | t.Fatal("should have value") 38 | } 39 | defer value.Close() 40 | 41 | if value.Type() != ObjectTypeString { 42 | t.Fatalf("bad: %#v", obj.Type()) 43 | } 44 | 45 | if value.Key() != "foo" { 46 | t.Fatalf("bad: %#v", value.Key()) 47 | } 48 | 49 | if value.ToString() != "bar" { 50 | t.Fatalf("bad: %#v", value.ToString()) 51 | } 52 | } 53 | 54 | func TestParserAddFile(t *testing.T) { 55 | tf, err := ioutil.TempFile("", "libucl") 56 | if err != nil { 57 | t.Fatalf("err: %s", err) 58 | } 59 | tf.Write([]byte("foo = bar;")) 60 | tf.Close() 61 | 62 | p := NewParser(0) 63 | defer p.Close() 64 | 65 | if err := p.AddFile(tf.Name()); err != nil { 66 | t.Fatalf("err: %s", err) 67 | } 68 | 69 | obj := p.Object() 70 | if obj == nil { 71 | t.Fatal("obj should not be nil") 72 | } 73 | defer obj.Close() 74 | 75 | if obj.Type() != ObjectTypeObject { 76 | t.Fatalf("bad: %#v", obj.Type()) 77 | } 78 | 79 | value := obj.Get("foo") 80 | if value == nil { 81 | t.Fatal("should have value") 82 | } 83 | defer value.Close() 84 | 85 | if value.Type() != ObjectTypeString { 86 | t.Fatalf("bad: %#v", obj.Type()) 87 | } 88 | 89 | if value.Key() != "foo" { 90 | t.Fatalf("bad: %#v", value.Key()) 91 | } 92 | 93 | if value.ToString() != "bar" { 94 | t.Fatalf("bad: %#v", value.ToString()) 95 | } 96 | } 97 | 98 | func TestParserRegisterMacro(t *testing.T) { 99 | value := "" 100 | macro := func(data string) { 101 | value = data 102 | } 103 | 104 | config := `.foo "bar";` 105 | 106 | p := NewParser(0) 107 | defer p.Close() 108 | 109 | p.RegisterMacro("foo", macro) 110 | 111 | if err := p.AddString(config); err != nil { 112 | t.Fatalf("err: %s", err) 113 | } 114 | 115 | if value != "bar" { 116 | t.Fatalf("bad: %#v", value) 117 | } 118 | } 119 | 120 | func TestParseString(t *testing.T) { 121 | obj, err := ParseString("foo = bar; baz = boo;") 122 | if err != nil { 123 | t.Fatalf("err: %s", err) 124 | } 125 | if obj == nil { 126 | t.Fatal("should have object") 127 | } 128 | defer obj.Close() 129 | 130 | if obj.Len() != 2 { 131 | t.Fatalf("bad: %d", obj.Len()) 132 | } 133 | } 134 | --------------------------------------------------------------------------------