198 lines
6.3 KiB
Go
198 lines
6.3 KiB
Go
package convert
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/qsfera/server/pkg/ast"
|
|
)
|
|
|
|
func ExpandKQL(nodes []ast.Node) ([]ast.Node, error) {
|
|
return kqlExpander{}.expand(nodes, "")
|
|
}
|
|
|
|
type kqlExpander struct{}
|
|
|
|
func (e kqlExpander) expand(nodes []ast.Node, defaultKey string) ([]ast.Node, error) {
|
|
for i, node := range nodes {
|
|
rnode := reflect.ValueOf(node)
|
|
|
|
// we need to ensure that the node is a pointer to an ast.Node in every case
|
|
if rnode.Kind() != reflect.Ptr {
|
|
ptr := reflect.New(rnode.Type())
|
|
ptr.Elem().Set(rnode)
|
|
rnode = ptr
|
|
cnode, ok := rnode.Interface().(ast.Node)
|
|
if !ok {
|
|
return nil, fmt.Errorf("expected node to be of type ast.Node, got %T", rnode.Interface())
|
|
}
|
|
|
|
node = cnode // Update the original node to the pointer
|
|
nodes[i] = node // Update the original slice with the pointer
|
|
}
|
|
|
|
var unfoldedNodes []ast.Node
|
|
switch cnode := node.(type) {
|
|
case *ast.GroupNode:
|
|
if cnode.Key != "" { // group nodes should not get a default key
|
|
cnode.Key = e.remapKey(cnode.Key, defaultKey)
|
|
}
|
|
|
|
groupNodes, err := e.expand(cnode.Nodes, cnode.Key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cnode.Nodes = groupNodes
|
|
case *ast.StringNode:
|
|
cnode.Key = e.remapKey(cnode.Key, defaultKey)
|
|
cnode.Value = e.lowerValue(cnode.Key, cnode.Value)
|
|
unfoldedNodes = e.unfoldValue(cnode.Key, cnode.Value)
|
|
case *ast.DateTimeNode:
|
|
cnode.Key = e.remapKey(cnode.Key, defaultKey)
|
|
case *ast.BooleanNode:
|
|
cnode.Key = e.remapKey(cnode.Key, defaultKey)
|
|
}
|
|
|
|
if unfoldedNodes != nil {
|
|
// Insert unfolded nodes at the current index
|
|
nodes = append(nodes[:i], append(unfoldedNodes, nodes[i+1:]...)...)
|
|
// Adjust index to account for new nodes
|
|
i += len(unfoldedNodes) - 1
|
|
}
|
|
}
|
|
|
|
return nodes, nil
|
|
}
|
|
|
|
func (_ kqlExpander) remapKey(current string, defaultKey string) string {
|
|
if defaultKey == "" {
|
|
defaultKey = "Name" // Set a default key if none is provided
|
|
}
|
|
|
|
key, ok := map[string]string{
|
|
"": defaultKey, // Default case if current is empty
|
|
"rootid": "RootID",
|
|
"path": "Path",
|
|
"id": "ID",
|
|
"name": "Name",
|
|
"size": "Size",
|
|
"mtime": "Mtime",
|
|
"mediatype": "MimeType",
|
|
"type": "Type",
|
|
"tag": "Tags",
|
|
"tags": "Tags",
|
|
"content": "Content",
|
|
"hidden": "Hidden",
|
|
}[current]
|
|
if !ok {
|
|
return current // Return the original key if not found
|
|
}
|
|
|
|
return key
|
|
}
|
|
|
|
func (_ kqlExpander) lowerValue(key, value string) string {
|
|
if slices.Contains([]string{"Hidden"}, key) {
|
|
return value // ignore certain keys and return the original value
|
|
}
|
|
|
|
return strings.ToLower(value)
|
|
}
|
|
|
|
func (_ kqlExpander) unfoldValue(key, value string) []ast.Node {
|
|
result, ok := map[string][]ast.Node{
|
|
"MimeType:file": {
|
|
&ast.OperatorNode{Value: "NOT"},
|
|
&ast.StringNode{Key: key, Value: "httpd/unix-directory"},
|
|
},
|
|
"MimeType:folder": {
|
|
&ast.StringNode{Key: key, Value: "httpd/unix-directory"},
|
|
},
|
|
"MimeType:document": {
|
|
&ast.GroupNode{Nodes: []ast.Node{
|
|
&ast.StringNode{Key: key, Value: "application/msword"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "application/vnd.openxmlformats-officedocument.wordprocessingml.form"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "application/vnd.oasis.opendocument.text"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "text/plain"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "text/markdown"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "application/rtf"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "application/vnd.apple.pages"},
|
|
}},
|
|
},
|
|
"MimeType:spreadsheet": {
|
|
&ast.GroupNode{Nodes: []ast.Node{
|
|
&ast.StringNode{Key: key, Value: "application/vnd.ms-excel"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "application/vnd.oasis.opendocument.spreadsheet"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "text/csv"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "application/vnd.oasis.opendocument.spreadshee"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "application/vnd.apple.numbers"},
|
|
}},
|
|
},
|
|
"MimeType:presentation": {
|
|
&ast.GroupNode{Nodes: []ast.Node{
|
|
&ast.StringNode{Key: key, Value: "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "application/vnd.oasis.opendocument.presentation"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "application/vnd.ms-powerpoint"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "application/vnd.apple.keynote"},
|
|
}},
|
|
},
|
|
"MimeType:pdf": {
|
|
&ast.StringNode{Key: key, Value: "application/pdf"},
|
|
},
|
|
"MimeType:image": {
|
|
&ast.StringNode{Key: key, Value: "image/*"},
|
|
},
|
|
"MimeType:video": {
|
|
&ast.StringNode{Key: key, Value: "video/*"},
|
|
},
|
|
"MimeType:audio": {
|
|
&ast.StringNode{Key: key, Value: "audio/*"},
|
|
},
|
|
"MimeType:archive": {
|
|
&ast.GroupNode{Nodes: []ast.Node{
|
|
&ast.StringNode{Key: key, Value: "application/zip"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "application/gzip"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "application/x-gzip"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "application/x-7z-compressed"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "application/x-rar-compressed"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "application/x-tar"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "application/x-bzip2"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "application/x-bzip"},
|
|
&ast.OperatorNode{Value: "OR"},
|
|
&ast.StringNode{Key: key, Value: "application/x-tgz"},
|
|
}},
|
|
},
|
|
}[fmt.Sprintf("%s:%s", key, value)]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
return result
|
|
}
|