├── .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 |
--------------------------------------------------------------------------------