github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/actions/lua/util/deep_pull.go (about)

     1  package util
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/Shopify/go-lua"
     8  )
     9  
    10  var (
    11  	ErrCannotPull     = errors.New("cannot pull go type into lua")
    12  	ErrStackExhausted = errors.New("pull table, stack exhausted")
    13  )
    14  
    15  func Open(l *lua.State) {
    16  	l.Register("array", luaArray)
    17  }
    18  
    19  func PullStringTable(l *lua.State, idx int) (map[string]string, error) {
    20  	if !l.IsTable(idx) {
    21  		return nil, fmt.Errorf("need a table at index %d, got %s: %w", idx, lua.TypeNameOf(l, idx), ErrCannotPull)
    22  	}
    23  
    24  	// Table at idx
    25  	l.PushNil() // Add free slot for the value, +1
    26  
    27  	table := make(map[string]string)
    28  	// -1:nil, idx:table
    29  	for l.Next(idx) {
    30  		// -1:val, -2:key, idx:table
    31  		key, ok := l.ToString(-2)
    32  		if !ok {
    33  			return nil, fmt.Errorf("key should be a string (%v): %w", l.ToValue(-2), ErrCannotPull)
    34  		}
    35  		val, ok := l.ToString(-1)
    36  		if !ok {
    37  			return nil, fmt.Errorf("value for key '%s' should be a string (%v): %w", key, l.ToValue(-1), ErrCannotPull)
    38  		}
    39  		table[key] = val
    40  		l.Pop(1) // remove val from top, -1
    41  		// -1:key, idx: table
    42  	}
    43  
    44  	return table, nil
    45  }
    46  
    47  func PullTable(l *lua.State, idx int) (interface{}, error) {
    48  	if !l.IsTable(idx) {
    49  		return nil, fmt.Errorf("need a table at index %d, got %s: %w", idx, lua.TypeNameOf(l, idx), ErrCannotPull)
    50  	}
    51  
    52  	return pullTableRec(l, idx)
    53  }
    54  
    55  func pullTableRec(l *lua.State, idx int) (interface{}, error) {
    56  	if !l.CheckStack(2) {
    57  		return nil, ErrStackExhausted
    58  	}
    59  
    60  	idx = l.AbsIndex(idx)
    61  	if isArray(l, idx) {
    62  		return pullArrayRec(l, idx)
    63  	}
    64  
    65  	table := make(map[string]interface{})
    66  
    67  	l.PushNil()
    68  	for l.Next(idx) {
    69  		// -1: value, -2: key, ..., idx: table
    70  		key, ok := l.ToString(-2)
    71  		if !ok {
    72  			err := fmt.Errorf("key should be a string (%s): %w", lua.TypeNameOf(l, -2), ErrCannotPull)
    73  			l.Pop(2)
    74  			return nil, err
    75  		}
    76  
    77  		value, err := toGoValue(l, -1)
    78  		if err != nil {
    79  			l.Pop(2)
    80  			return nil, err
    81  		}
    82  
    83  		table[key] = value
    84  
    85  		l.Pop(1)
    86  	}
    87  
    88  	return table, nil
    89  }
    90  
    91  const arrayMarkerField = "_is_array"
    92  
    93  func luaArray(l *lua.State) int {
    94  	l.NewTable()
    95  	l.PushBoolean(true)
    96  	l.SetField(-2, arrayMarkerField)
    97  	l.SetMetaTable(-2)
    98  	return 1
    99  }
   100  
   101  func isArray(l *lua.State, idx int) bool {
   102  	if !l.IsTable(idx) {
   103  		return false
   104  	}
   105  
   106  	if !lua.MetaField(l, idx, arrayMarkerField) {
   107  		return false
   108  	}
   109  	defer l.Pop(1)
   110  
   111  	return l.ToBoolean(-1)
   112  }
   113  
   114  func pullArrayRec(l *lua.State, idx int) (interface{}, error) {
   115  	table := make([]interface{}, lua.LengthEx(l, idx))
   116  
   117  	l.PushNil()
   118  	for l.Next(idx) {
   119  		k, ok := l.ToInteger(-2)
   120  		if !ok {
   121  			l.Pop(2)
   122  			return nil, fmt.Errorf("pull array: expected numeric index, got '%s': %w", l.TypeOf(-2), ErrCannotPull)
   123  		}
   124  
   125  		v, err := toGoValue(l, -1)
   126  		if err != nil {
   127  			l.Pop(2)
   128  			return nil, err
   129  		}
   130  
   131  		table[k-1] = v
   132  		l.Pop(1)
   133  	}
   134  
   135  	return table, nil
   136  }
   137  
   138  func toGoValue(l *lua.State, idx int) (interface{}, error) {
   139  	t := l.TypeOf(idx)
   140  	switch t {
   141  	case lua.TypeBoolean:
   142  		return l.ToBoolean(idx), nil
   143  	case lua.TypeString:
   144  		return lua.CheckString(l, idx), nil
   145  	case lua.TypeNumber:
   146  		return lua.CheckNumber(l, idx), nil
   147  	case lua.TypeTable:
   148  		return pullTableRec(l, idx)
   149  	default:
   150  		err := fmt.Errorf("pull table, unsupported type %s: %w", lua.TypeNameOf(l, idx), ErrCannotPull)
   151  		return nil, err
   152  	}
   153  }