mirror of
https://github.com/turbot/steampipe.git
synced 2026-02-16 16:00:11 -05:00
186 lines
4.4 KiB
Go
186 lines
4.4 KiB
Go
package parse
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
|
"github.com/turbot/pipe-fittings/hclhelpers"
|
|
"github.com/turbot/steampipe-plugin-sdk/v5/plugin"
|
|
"github.com/turbot/steampipe/pkg/steampipeconfig/modconfig"
|
|
)
|
|
|
|
// ParseQueryInvocation parses a query invocation and extracts the args (if any)
|
|
// supported formats are:
|
|
//
|
|
// 1) positional args
|
|
// query.my_prepared_statement('val1','val1')
|
|
//
|
|
// 2) named args
|
|
// query.my_prepared_statement(my_arg1 => 'test', my_arg2 => 'test2')
|
|
func ParseQueryInvocation(arg string) (string, *modconfig.QueryArgs, error) {
|
|
// TODO strip non printing chars
|
|
args := &modconfig.QueryArgs{}
|
|
|
|
arg = strings.TrimSpace(arg)
|
|
query := arg
|
|
var err error
|
|
openBracketIdx := strings.Index(arg, "(")
|
|
closeBracketIdx := strings.LastIndex(arg, ")")
|
|
if openBracketIdx != -1 && closeBracketIdx == len(arg)-1 {
|
|
argsString := arg[openBracketIdx+1 : len(arg)-1]
|
|
args, err = parseArgs(argsString)
|
|
query = strings.TrimSpace(arg[:openBracketIdx])
|
|
}
|
|
return query, args, err
|
|
}
|
|
|
|
// parse the actual args string, i.e. the contents of the bracket
|
|
// supported formats are:
|
|
//
|
|
// 1) positional args
|
|
// 'val1','val1'
|
|
//
|
|
// 2) named args
|
|
// my_arg1 => 'val1', my_arg2 => 'val2'
|
|
func parseArgs(argsString string) (*modconfig.QueryArgs, error) {
|
|
res := modconfig.NewQueryArgs()
|
|
if len(argsString) == 0 {
|
|
return res, nil
|
|
}
|
|
|
|
// split on comma to get each arg string (taking quotes and brackets into account)
|
|
splitArgs, err := splitArgString(argsString)
|
|
if err != nil {
|
|
// return empty result, even if we have an error
|
|
return res, err
|
|
}
|
|
|
|
// first check for named args
|
|
argMap, err := parseNamedArgs(splitArgs)
|
|
if err != nil {
|
|
return res, err
|
|
}
|
|
if err := res.SetArgMap(argMap); err != nil {
|
|
return res, err
|
|
}
|
|
|
|
if res.Empty() {
|
|
// no named args - fall back on positional
|
|
argList, err := parsePositionalArgs(splitArgs)
|
|
if err != nil {
|
|
return res, err
|
|
}
|
|
if err := res.SetArgList(argList); err != nil {
|
|
return res, err
|
|
}
|
|
}
|
|
// return empty result, even if we have an error
|
|
return res, err
|
|
}
|
|
|
|
func splitArgString(argsString string) ([]string, error) {
|
|
var argsList []string
|
|
openElements := map[string]int{
|
|
"quote": 0,
|
|
"curly": 0,
|
|
"square": 0,
|
|
}
|
|
var currentWord string
|
|
for _, c := range argsString {
|
|
// should we split - are we in a block
|
|
if c == ',' &&
|
|
openElements["quote"] == 0 && openElements["curly"] == 0 && openElements["square"] == 0 {
|
|
if len(currentWord) > 0 {
|
|
argsList = append(argsList, currentWord)
|
|
currentWord = ""
|
|
}
|
|
} else {
|
|
currentWord = currentWord + string(c)
|
|
}
|
|
|
|
// handle brackets and quotes
|
|
switch c {
|
|
case '{':
|
|
if openElements["quote"] == 0 {
|
|
openElements["curly"]++
|
|
}
|
|
case '}':
|
|
if openElements["quote"] == 0 {
|
|
openElements["curly"]--
|
|
if openElements["curly"] < 0 {
|
|
return nil, fmt.Errorf("bad arg syntax")
|
|
}
|
|
}
|
|
case '[':
|
|
if openElements["quote"] == 0 {
|
|
openElements["square"]++
|
|
}
|
|
case ']':
|
|
if openElements["quote"] == 0 {
|
|
openElements["square"]--
|
|
if openElements["square"] < 0 {
|
|
return nil, fmt.Errorf("bad arg syntax")
|
|
}
|
|
}
|
|
case '"':
|
|
if openElements["quote"] == 0 {
|
|
openElements["quote"] = 1
|
|
} else {
|
|
openElements["quote"] = 0
|
|
}
|
|
}
|
|
}
|
|
if len(currentWord) > 0 {
|
|
argsList = append(argsList, currentWord)
|
|
}
|
|
return argsList, nil
|
|
}
|
|
|
|
func parseArg(v string) (any, error) {
|
|
b, diags := hclsyntax.ParseExpression([]byte(v), "", hcl.Pos{})
|
|
if diags.HasErrors() {
|
|
return "", plugin.DiagsToError("bad arg syntax", diags)
|
|
}
|
|
val, diags := b.Value(nil)
|
|
if diags.HasErrors() {
|
|
return "", plugin.DiagsToError("bad arg syntax", diags)
|
|
}
|
|
return hclhelpers.CtyToGo(val)
|
|
}
|
|
|
|
func parseNamedArgs(argsList []string) (map[string]any, error) {
|
|
var res = make(map[string]any)
|
|
for _, p := range argsList {
|
|
argTuple := strings.Split(strings.TrimSpace(p), "=>")
|
|
if len(argTuple) != 2 {
|
|
// not all args have valid syntax - give up
|
|
return nil, nil
|
|
}
|
|
k := strings.TrimSpace(argTuple[0])
|
|
val, err := parseArg(argTuple[1])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res[k] = val
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func parsePositionalArgs(argsList []string) ([]any, error) {
|
|
// convert to pointer array
|
|
res := make([]any, len(argsList))
|
|
// just treat args as positional args
|
|
// strip spaces
|
|
for i, v := range argsList {
|
|
valStr, err := parseArg(v)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res[i] = valStr
|
|
}
|
|
|
|
return res, nil
|
|
}
|