├── .travis.yml ├── gluamapper_example_test.go ├── LICENSE ├── README.rst ├── gluamapper.go └── gluamapper_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.3 5 | - 1.4 6 | 7 | install: 8 | - go get github.com/mitchellh/mapstructure 9 | - go get github.com/yuin/gopher-lua 10 | 11 | script: 12 | - go test -v 13 | -------------------------------------------------------------------------------- /gluamapper_example_test.go: -------------------------------------------------------------------------------- 1 | package gluamapper 2 | 3 | import ( 4 | "fmt" 5 | "github.com/yuin/gopher-lua" 6 | ) 7 | 8 | func ExampleMap() { 9 | type Role struct { 10 | Name string 11 | } 12 | 13 | type Person struct { 14 | Name string 15 | Age int 16 | WorkPlace string 17 | Role []*Role 18 | } 19 | 20 | L := lua.NewState() 21 | if err := L.DoString(` 22 | person = { 23 | name = "Michel", 24 | age = "31", -- weakly input 25 | work_place = "San Jose", 26 | role = { 27 | { 28 | name = "Administrator" 29 | }, 30 | { 31 | name = "Operator" 32 | } 33 | } 34 | } 35 | `); err != nil { 36 | panic(err) 37 | } 38 | var person Person 39 | if err := Map(L.GetGlobal("person").(*lua.LTable), &person); err != nil { 40 | panic(err) 41 | } 42 | fmt.Printf("%s %d", person.Name, person.Age) 43 | // Output: 44 | // Michel 31 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Yusuke Inuzuka 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.rst: -------------------------------------------------------------------------------- 1 | =============================================================================== 2 | gluamapper: maps a GopherLua table to a Go struct 3 | =============================================================================== 4 | 5 | .. image:: https://godoc.org/github.com/yuin/gluamapper?status.svg 6 | :target: http://godoc.org/github.com/yuin/gluamapper 7 | 8 | .. image:: https://travis-ci.org/yuin/gluamapper.svg 9 | :target: https://travis-ci.org/yuin/gluamapper 10 | 11 | | 12 | 13 | gluamapper provides an easy way to map GopherLua tables to Go structs. 14 | 15 | gluamapper converts a GopherLua table to ``map[string]interface{}`` , and then converts it to a Go struct using `mapstructure `_ . 16 | 17 | ---------------------------------------------------------------- 18 | Installation 19 | ---------------------------------------------------------------- 20 | 21 | .. code-block:: bash 22 | 23 | go get github.com/yuin/gluamapper 24 | 25 | ---------------------------------------------------------------- 26 | API 27 | ---------------------------------------------------------------- 28 | See `Go doc `_ . 29 | 30 | ---------------------------------------------------------------- 31 | Usage 32 | ---------------------------------------------------------------- 33 | 34 | .. code-block:: go 35 | 36 | type Role struct { 37 | Name string 38 | } 39 | 40 | type Person struct { 41 | Name string 42 | Age int 43 | WorkPlace string 44 | Role []*Role 45 | } 46 | 47 | L := lua.NewState() 48 | if err := L.DoString(` 49 | person = { 50 | name = "Michel", 51 | age = "31", -- weakly input 52 | work_place = "San Jose", 53 | role = { 54 | { 55 | name = "Administrator" 56 | }, 57 | { 58 | name = "Operator" 59 | } 60 | } 61 | } 62 | `); err != nil { 63 | panic(err) 64 | } 65 | var person Person 66 | if err := gluamapper.Map(L.GetGlobal("person").(*lua.LTable), &person); err != nil { 67 | panic(err) 68 | } 69 | fmt.Printf("%s %d", person.Name, person.Age) 70 | 71 | ---------------------------------------------------------------- 72 | License 73 | ---------------------------------------------------------------- 74 | MIT 75 | 76 | ---------------------------------------------------------------- 77 | Author 78 | ---------------------------------------------------------------- 79 | Yusuke Inuzuka 80 | -------------------------------------------------------------------------------- /gluamapper.go: -------------------------------------------------------------------------------- 1 | // gluamapper provides an easy way to map GopherLua tables to Go structs. 2 | package gluamapper 3 | 4 | import ( 5 | "errors" 6 | "fmt" 7 | "github.com/mitchellh/mapstructure" 8 | "github.com/yuin/gopher-lua" 9 | "regexp" 10 | "strings" 11 | ) 12 | 13 | // Option is a configuration that is used to create a new mapper. 14 | type Option struct { 15 | // Function to convert a lua table key to Go's one. This defaults to "ToUpperCamelCase". 16 | NameFunc func(string) string 17 | 18 | // Returns error if unused keys exist. 19 | ErrorUnused bool 20 | 21 | // A struct tag name for lua table keys . This defaults to "gluamapper" 22 | TagName string 23 | } 24 | 25 | // Mapper maps a lua table to a Go struct pointer. 26 | type Mapper struct { 27 | Option Option 28 | } 29 | 30 | // NewMapper returns a new mapper. 31 | func NewMapper(opt Option) *Mapper { 32 | if opt.NameFunc == nil { 33 | opt.NameFunc = ToUpperCamelCase 34 | } 35 | if opt.TagName == "" { 36 | opt.TagName = "gluamapper" 37 | } 38 | return &Mapper{opt} 39 | } 40 | 41 | // Map maps the lua table to the given struct pointer. 42 | func (mapper *Mapper) Map(tbl *lua.LTable, st interface{}) error { 43 | opt := mapper.Option 44 | mp, ok := ToGoValue(tbl, opt).(map[interface{}]interface{}) 45 | if !ok { 46 | return errors.New("arguments #1 must be a table, but got an array") 47 | } 48 | config := &mapstructure.DecoderConfig{ 49 | WeaklyTypedInput: true, 50 | Result: st, 51 | TagName: opt.TagName, 52 | ErrorUnused: opt.ErrorUnused, 53 | } 54 | decoder, err := mapstructure.NewDecoder(config) 55 | if err != nil { 56 | return err 57 | } 58 | return decoder.Decode(mp) 59 | } 60 | 61 | // Map maps the lua table to the given struct pointer with default options. 62 | func Map(tbl *lua.LTable, st interface{}) error { 63 | return NewMapper(Option{}).Map(tbl, st) 64 | } 65 | 66 | 67 | // Id is an Option.NameFunc that returns given string as-is. 68 | func Id(s string) string { 69 | return s 70 | } 71 | 72 | var camelre = regexp.MustCompile(`_([a-z])`) 73 | // ToUpperCamelCase is an Option.NameFunc that converts strings from snake case to upper camel case. 74 | func ToUpperCamelCase(s string) string { 75 | return strings.ToUpper(string(s[0])) + camelre.ReplaceAllStringFunc(s[1:len(s)], func(s string) string { return strings.ToUpper(s[1:len(s)]) }) 76 | } 77 | 78 | // ToGoValue converts the given LValue to a Go object. 79 | func ToGoValue(lv lua.LValue, opt Option) interface{} { 80 | switch v := lv.(type) { 81 | case *lua.LNilType: 82 | return nil 83 | case lua.LBool: 84 | return bool(v) 85 | case lua.LString: 86 | return string(v) 87 | case lua.LNumber: 88 | return float64(v) 89 | case *lua.LTable: 90 | maxn := v.MaxN() 91 | if maxn == 0 { // table 92 | ret := make(map[interface{}]interface{}) 93 | v.ForEach(func(key, value lua.LValue) { 94 | keystr := fmt.Sprint(ToGoValue(key, opt)) 95 | ret[opt.NameFunc(keystr)] = ToGoValue(value, opt) 96 | }) 97 | return ret 98 | } else { // array 99 | ret := make([]interface{}, 0, maxn) 100 | for i := 1; i <= maxn; i++ { 101 | ret = append(ret, ToGoValue(v.RawGetInt(i), opt)) 102 | } 103 | return ret 104 | } 105 | default: 106 | return v 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /gluamapper_test.go: -------------------------------------------------------------------------------- 1 | package gluamapper 2 | 3 | import ( 4 | "github.com/yuin/gopher-lua" 5 | "path/filepath" 6 | "runtime" 7 | "testing" 8 | ) 9 | 10 | func errorIfNotEqual(t *testing.T, v1, v2 interface{}) { 11 | if v1 != v2 { 12 | _, file, line, _ := runtime.Caller(1) 13 | t.Errorf("%v line %v: '%v' expected, but got '%v'", filepath.Base(file), line, v1, v2) 14 | } 15 | } 16 | 17 | type testRole struct { 18 | Name string 19 | } 20 | 21 | type testPerson struct { 22 | Name string 23 | Age int 24 | WorkPlace string 25 | Role []*testRole 26 | } 27 | 28 | type testStruct struct { 29 | Nil interface{} 30 | Bool bool 31 | String string 32 | Number int `gluamapper:"number_value"` 33 | Func interface{} 34 | } 35 | 36 | func TestMap(t *testing.T) { 37 | L := lua.NewState() 38 | if err := L.DoString(` 39 | person = { 40 | name = "Michel", 41 | age = "31", -- weakly input 42 | work_place = "San Jose", 43 | role = { 44 | { 45 | name = "Administrator" 46 | }, 47 | { 48 | name = "Operator" 49 | } 50 | } 51 | } 52 | `); err != nil { 53 | t.Error(err) 54 | } 55 | var person testPerson 56 | if err := Map(L.GetGlobal("person").(*lua.LTable), &person); err != nil { 57 | t.Error(err) 58 | } 59 | errorIfNotEqual(t, "Michel", person.Name) 60 | errorIfNotEqual(t, 31, person.Age) 61 | errorIfNotEqual(t, "San Jose", person.WorkPlace) 62 | errorIfNotEqual(t, 2, len(person.Role)) 63 | errorIfNotEqual(t, "Administrator", person.Role[0].Name) 64 | errorIfNotEqual(t, "Operator", person.Role[1].Name) 65 | } 66 | 67 | func TestTypes(t *testing.T) { 68 | L := lua.NewState() 69 | if err := L.DoString(` 70 | tbl = { 71 | ["Nil"] = nil, 72 | ["Bool"] = true, 73 | ["String"] = "string", 74 | ["Number_value"] = 10, 75 | ["Func"] = function() end 76 | } 77 | `); err != nil { 78 | t.Error(err) 79 | } 80 | var stct testStruct 81 | 82 | if err := NewMapper(Option{NameFunc: Id}).Map(L.GetGlobal("tbl").(*lua.LTable), &stct); err != nil { 83 | t.Error(err) 84 | } 85 | errorIfNotEqual(t, nil, stct.Nil) 86 | errorIfNotEqual(t, true, stct.Bool) 87 | errorIfNotEqual(t, "string", stct.String) 88 | errorIfNotEqual(t, 10, stct.Number) 89 | } 90 | 91 | func TestNameFunc(t *testing.T) { 92 | L := lua.NewState() 93 | if err := L.DoString(` 94 | person = { 95 | Name = "Michel", 96 | Age = "31", -- weekly input 97 | WorkPlace = "San Jose", 98 | Role = { 99 | { 100 | Name = "Administrator" 101 | }, 102 | { 103 | Name = "Operator" 104 | } 105 | } 106 | } 107 | `); err != nil { 108 | t.Error(err) 109 | } 110 | var person testPerson 111 | mapper := NewMapper(Option{NameFunc: Id}) 112 | if err := mapper.Map(L.GetGlobal("person").(*lua.LTable), &person); err != nil { 113 | t.Error(err) 114 | } 115 | errorIfNotEqual(t, "Michel", person.Name) 116 | errorIfNotEqual(t, 31, person.Age) 117 | errorIfNotEqual(t, "San Jose", person.WorkPlace) 118 | errorIfNotEqual(t, 2, len(person.Role)) 119 | errorIfNotEqual(t, "Administrator", person.Role[0].Name) 120 | errorIfNotEqual(t, "Operator", person.Role[1].Name) 121 | } 122 | 123 | func TestError(t *testing.T) { 124 | L := lua.NewState() 125 | tbl := L.NewTable() 126 | L.SetField(tbl, "key", lua.LString("value")) 127 | err := Map(tbl, 1) 128 | if err.Error() != "result must be a pointer" { 129 | t.Error("invalid error message") 130 | } 131 | 132 | tbl = L.NewTable() 133 | tbl.Append(lua.LNumber(1)) 134 | var person testPerson 135 | err = Map(tbl, &person) 136 | if err.Error() != "arguments #1 must be a table, but got an array" { 137 | t.Error("invalid error message") 138 | } 139 | } 140 | --------------------------------------------------------------------------------