|
@@ -0,0 +1,335 @@
|
|
|
+/*
|
|
|
+ * Public Domain Software
|
|
|
+ *
|
|
|
+ * I (Matthias Ladkau) am the author of the source code in this file.
|
|
|
+ * I have placed the source code in this file in the public domain.
|
|
|
+ *
|
|
|
+ * For further information see: http://creativecommons.org/publicdomain/zero/1.0/
|
|
|
+ */
|
|
|
+
|
|
|
+package parser
|
|
|
+
|
|
|
+import (
|
|
|
+ "bytes"
|
|
|
+ "fmt"
|
|
|
+ "regexp"
|
|
|
+ "strconv"
|
|
|
+ "strings"
|
|
|
+ "text/template"
|
|
|
+ "unicode"
|
|
|
+
|
|
|
+ "devt.de/krotik/common/errorutil"
|
|
|
+ "devt.de/krotik/common/stringutil"
|
|
|
+)
|
|
|
+
|
|
|
+/*
|
|
|
+IndentationLevel is the level of indentation which the pretty printer should use
|
|
|
+*/
|
|
|
+const IndentationLevel = 2
|
|
|
+
|
|
|
+/*
|
|
|
+Map of pretty printer templates for AST nodes
|
|
|
+
|
|
|
+There is special treatment for NodeVALUE.
|
|
|
+*/
|
|
|
+var prettyPrinterMap = map[string]*template.Template{
|
|
|
+ NodeArgument + "_2": template.Must(template.New(NodeArgument).Parse("{{.c1}}: {{.c2}}")),
|
|
|
+
|
|
|
+ NodeOperationDefinition + "_1": template.Must(template.New(NodeArgument).Parse("{{.c1}}")),
|
|
|
+ NodeOperationDefinition + "_2": template.Must(template.New(NodeArgument).Parse("{{.c1}} {{.c2}}")),
|
|
|
+ NodeOperationDefinition + "_3": template.Must(template.New(NodeArgument).Parse("{{.c1}} {{.c2}} {{.c3}}")),
|
|
|
+ NodeOperationDefinition + "_4": template.Must(template.New(NodeArgument).Parse("{{.c1}} {{.c2}} {{.c3}} {{.c4}}")),
|
|
|
+ NodeOperationDefinition + "_5": template.Must(template.New(NodeArgument).Parse("{{.c1}} {{.c2}} {{.c3}} {{.c4}} {{.c5}}")),
|
|
|
+
|
|
|
+ NodeFragmentDefinition + "_3": template.Must(template.New(NodeArgument).Parse("fragment {{.c1}} {{.c2}} {{.c3}}")),
|
|
|
+ NodeFragmentDefinition + "_4": template.Must(template.New(NodeArgument).Parse("fragment {{.c1}} {{.c2}} {{.c3}} {{.c4}}")),
|
|
|
+
|
|
|
+ NodeInlineFragment + "_1": template.Must(template.New(NodeArgument).Parse("... {{.c1}}\n")),
|
|
|
+ NodeInlineFragment + "_2": template.Must(template.New(NodeArgument).Parse("... {{.c1}} {{.c2}}\n")),
|
|
|
+ NodeInlineFragment + "_3": template.Must(template.New(NodeArgument).Parse("... {{.c1}} {{.c2}} {{.c3}}\n")),
|
|
|
+
|
|
|
+ NodeExecutableDefinition + "_1": template.Must(template.New(NodeArgument).Parse("{{.c1}}")),
|
|
|
+
|
|
|
+ NodeVariableDefinition + "_2": template.Must(template.New(NodeArgument).Parse("{{.c1}}: {{.c2}}")),
|
|
|
+ NodeVariableDefinition + "_3": template.Must(template.New(NodeArgument).Parse("{{.c1}}: {{.c2}}{{.c3}}")),
|
|
|
+
|
|
|
+ NodeDirective + "_1": template.Must(template.New(NodeArgument).Parse("@{{.c1}}")),
|
|
|
+ NodeDirective + "_2": template.Must(template.New(NodeArgument).Parse("@{{.c1}}{{.c2}}")),
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+PrettyPrint produces a pretty printed EQL query from a given AST.
|
|
|
+*/
|
|
|
+func PrettyPrint(ast *ASTNode) (string, error) {
|
|
|
+ var visit func(ast *ASTNode, path []*ASTNode) (string, error)
|
|
|
+
|
|
|
+ quoteValue := func(val string, allowNonQuotation bool) string {
|
|
|
+
|
|
|
+ if val == "" {
|
|
|
+ return `""`
|
|
|
+ }
|
|
|
+
|
|
|
+ isNumber, _ := regexp.MatchString("^[0-9][0-9\\.e-+]*$", val)
|
|
|
+ isInlineString, _ := regexp.MatchString("^[a-zA-Z0-9_:.]*$", val)
|
|
|
+
|
|
|
+ if allowNonQuotation && (isNumber || isInlineString) {
|
|
|
+ return val
|
|
|
+ } else if strings.ContainsRune(val, '"') {
|
|
|
+ val = strings.Replace(val, "\"", "\\\"", -1)
|
|
|
+ }
|
|
|
+ if strings.Contains(val, "\n") {
|
|
|
+ return fmt.Sprintf("\"\"\"%v\"\"\"", val)
|
|
|
+ }
|
|
|
+ return fmt.Sprintf("\"%v\"", val)
|
|
|
+ }
|
|
|
+
|
|
|
+ visit = func(ast *ASTNode, path []*ASTNode) (string, error) {
|
|
|
+
|
|
|
+ // Handle special cases which don't have children but values
|
|
|
+
|
|
|
+ if ast.Name == NodeValue {
|
|
|
+ v := ast.Token.Val
|
|
|
+
|
|
|
+ _, err := strconv.ParseFloat(v, 32)
|
|
|
+ isNum := err == nil
|
|
|
+
|
|
|
+ isConst := stringutil.IndexOf(v, []string{
|
|
|
+ "true", "false", "null",
|
|
|
+ }) != -1
|
|
|
+
|
|
|
+ return quoteValue(ast.Token.Val, isConst || isNum), nil
|
|
|
+
|
|
|
+ } else if ast.Name == NodeVariable {
|
|
|
+ return fmt.Sprintf("$%v", ast.Token.Val), nil
|
|
|
+ } else if ast.Name == NodeAlias {
|
|
|
+ return fmt.Sprintf("%v :", ast.Token.Val), nil
|
|
|
+ } else if ast.Name == NodeFragmentSpread {
|
|
|
+ return ppPostProcessing(ast, path, fmt.Sprintf("...%v\n", ast.Token.Val)), nil
|
|
|
+ } else if ast.Name == NodeTypeCondition {
|
|
|
+ return fmt.Sprintf("on %v", ast.Token.Val), nil
|
|
|
+ } else if ast.Name == NodeDefaultValue {
|
|
|
+ return fmt.Sprintf("=%v", ast.Token.Val), nil
|
|
|
+ }
|
|
|
+
|
|
|
+ var children map[string]string
|
|
|
+ var tempKey = ast.Name
|
|
|
+ var buf bytes.Buffer
|
|
|
+
|
|
|
+ // First pretty print children
|
|
|
+
|
|
|
+ if len(ast.Children) > 0 {
|
|
|
+ children = make(map[string]string)
|
|
|
+ for i, child := range ast.Children {
|
|
|
+ res, err := visit(child, append(path, child))
|
|
|
+ if err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+
|
|
|
+ children[fmt.Sprint("c", i+1)] = res
|
|
|
+ }
|
|
|
+
|
|
|
+ tempKey += fmt.Sprint("_", len(children))
|
|
|
+ }
|
|
|
+
|
|
|
+ // Handle special cases requiring children
|
|
|
+
|
|
|
+ if ast.Name == NodeDocument {
|
|
|
+ if children != nil {
|
|
|
+ i := 1
|
|
|
+ for ; i < len(children); i++ {
|
|
|
+ buf.WriteString(children[fmt.Sprint("c", i)])
|
|
|
+
|
|
|
+ if ast.Children[i].Name != NodeArguments {
|
|
|
+ buf.WriteString("\n\n")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ buf.WriteString(children[fmt.Sprint("c", i)])
|
|
|
+ }
|
|
|
+
|
|
|
+ return ppPostProcessing(ast, path, buf.String()), nil
|
|
|
+
|
|
|
+ } else if ast.Name == NodeOperationType || ast.Name == NodeName ||
|
|
|
+ ast.Name == NodeFragmentName || ast.Name == NodeType || ast.Name == NodeEnumValue {
|
|
|
+
|
|
|
+ return ast.Token.Val, nil
|
|
|
+
|
|
|
+ } else if ast.Name == NodeArguments {
|
|
|
+
|
|
|
+ buf.WriteString("(")
|
|
|
+
|
|
|
+ if children != nil {
|
|
|
+ i := 1
|
|
|
+ for ; i < len(children); i++ {
|
|
|
+ buf.WriteString(children[fmt.Sprint("c", i)])
|
|
|
+ buf.WriteString(", ")
|
|
|
+ }
|
|
|
+ buf.WriteString(children[fmt.Sprint("c", i)])
|
|
|
+ }
|
|
|
+ buf.WriteString(")")
|
|
|
+
|
|
|
+ return ppPostProcessing(ast, path, buf.String()), nil
|
|
|
+
|
|
|
+ } else if ast.Name == NodeListValue {
|
|
|
+ buf.WriteString("[")
|
|
|
+ if children != nil {
|
|
|
+ i := 1
|
|
|
+ for ; i < len(children); i++ {
|
|
|
+ buf.WriteString(children[fmt.Sprint("c", i)])
|
|
|
+ buf.WriteString(", ")
|
|
|
+ }
|
|
|
+ buf.WriteString(children[fmt.Sprint("c", i)])
|
|
|
+ }
|
|
|
+ buf.WriteString("]")
|
|
|
+
|
|
|
+ return ppPostProcessing(ast, path, buf.String()), nil
|
|
|
+
|
|
|
+ } else if ast.Name == NodeVariableDefinitions {
|
|
|
+ buf.WriteString("(")
|
|
|
+ if children != nil {
|
|
|
+ i := 1
|
|
|
+ for ; i < len(children); i++ {
|
|
|
+ buf.WriteString(children[fmt.Sprint("c", i)])
|
|
|
+ buf.WriteString(", ")
|
|
|
+ }
|
|
|
+ buf.WriteString(children[fmt.Sprint("c", i)])
|
|
|
+ }
|
|
|
+ buf.WriteString(")")
|
|
|
+
|
|
|
+ return ppPostProcessing(ast, path, buf.String()), nil
|
|
|
+
|
|
|
+ } else if ast.Name == NodeSelectionSet {
|
|
|
+ buf.WriteString("{\n")
|
|
|
+ if children != nil {
|
|
|
+ i := 1
|
|
|
+ for ; i < len(children); i++ {
|
|
|
+ buf.WriteString(children[fmt.Sprint("c", i)])
|
|
|
+ }
|
|
|
+ buf.WriteString(children[fmt.Sprint("c", i)])
|
|
|
+ }
|
|
|
+ buf.WriteString("}")
|
|
|
+
|
|
|
+ return ppPostProcessing(ast, path, buf.String()), nil
|
|
|
+
|
|
|
+ } else if ast.Name == NodeObjectValue {
|
|
|
+
|
|
|
+ buf.WriteString("{")
|
|
|
+
|
|
|
+ if children != nil {
|
|
|
+ i := 1
|
|
|
+ for ; i < len(children); i++ {
|
|
|
+ buf.WriteString(children[fmt.Sprint("c", i)])
|
|
|
+ buf.WriteString(", ")
|
|
|
+ }
|
|
|
+ buf.WriteString(children[fmt.Sprint("c", i)])
|
|
|
+ }
|
|
|
+ buf.WriteString("}")
|
|
|
+
|
|
|
+ return ppPostProcessing(ast, path, buf.String()), nil
|
|
|
+
|
|
|
+ } else if ast.Name == NodeObjectField {
|
|
|
+
|
|
|
+ buf.WriteString(ast.Token.Val)
|
|
|
+ buf.WriteString(" : ")
|
|
|
+ buf.WriteString(children["c1"])
|
|
|
+
|
|
|
+ return buf.String(), nil
|
|
|
+
|
|
|
+ } else if ast.Name == NodeField {
|
|
|
+
|
|
|
+ if children != nil {
|
|
|
+ i := 1
|
|
|
+ for ; i < len(children); i++ {
|
|
|
+ buf.WriteString(children[fmt.Sprint("c", i)])
|
|
|
+
|
|
|
+ if ast.Children[i].Name != NodeArguments {
|
|
|
+ buf.WriteString(" ")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ buf.WriteString(children[fmt.Sprint("c", i)])
|
|
|
+ buf.WriteString("\n")
|
|
|
+ }
|
|
|
+
|
|
|
+ return ppPostProcessing(ast, path, buf.String()), nil
|
|
|
+ } else if ast.Name == NodeDirectives {
|
|
|
+
|
|
|
+ if children != nil {
|
|
|
+ i := 1
|
|
|
+ for ; i < len(children); i++ {
|
|
|
+ buf.WriteString(children[fmt.Sprint("c", i)])
|
|
|
+
|
|
|
+ if ast.Children[i].Name != NodeArguments {
|
|
|
+ buf.WriteString(" ")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ buf.WriteString(children[fmt.Sprint("c", i)])
|
|
|
+ }
|
|
|
+
|
|
|
+ return ppPostProcessing(ast, path, buf.String()), nil
|
|
|
+ }
|
|
|
+
|
|
|
+ // Retrieve the template
|
|
|
+
|
|
|
+ temp, ok := prettyPrinterMap[tempKey]
|
|
|
+ if !ok {
|
|
|
+ return "", fmt.Errorf("Could not find template for %v (tempkey: %v)",
|
|
|
+ ast.Name, tempKey)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Use the children as parameters for template
|
|
|
+
|
|
|
+ errorutil.AssertOk(temp.Execute(&buf, children))
|
|
|
+
|
|
|
+ return ppPostProcessing(ast, path, buf.String()), nil
|
|
|
+ }
|
|
|
+
|
|
|
+ res, err := visit(ast, []*ASTNode{ast})
|
|
|
+
|
|
|
+ return strings.TrimSpace(res), err
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ppPostProcessing applies post processing rules.
|
|
|
+*/
|
|
|
+func ppPostProcessing(ast *ASTNode, path []*ASTNode, ppString string) string {
|
|
|
+ ret := ppString
|
|
|
+
|
|
|
+ // Apply indentation
|
|
|
+
|
|
|
+ if len(path) > 1 {
|
|
|
+ if stringutil.IndexOf(ast.Name, []string{
|
|
|
+ NodeField,
|
|
|
+ NodeFragmentSpread,
|
|
|
+ NodeInlineFragment,
|
|
|
+ }) != -1 {
|
|
|
+
|
|
|
+ parent := path[len(path)-3]
|
|
|
+
|
|
|
+ indentSpaces := stringutil.GenerateRollingString(" ", IndentationLevel)
|
|
|
+ ret = strings.ReplaceAll(ret, "\n", "\n"+indentSpaces)
|
|
|
+ ret = fmt.Sprintf("%v%v", indentSpaces, ret)
|
|
|
+
|
|
|
+ // Remove indentation from last line unless we have a special case
|
|
|
+
|
|
|
+ if stringutil.IndexOf(parent.Name, []string{
|
|
|
+ NodeField,
|
|
|
+ NodeOperationDefinition,
|
|
|
+ }) == -1 {
|
|
|
+
|
|
|
+ if idx := strings.LastIndex(ret, "\n"); idx != -1 {
|
|
|
+ ret = ret[:idx+1] + ret[idx+IndentationLevel+1:]
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Remove all trailing spaces
|
|
|
+
|
|
|
+ newlineSplit := strings.Split(ret, "\n")
|
|
|
+
|
|
|
+ for i, s := range newlineSplit {
|
|
|
+ newlineSplit[i] = strings.TrimRightFunc(s, unicode.IsSpace)
|
|
|
+ }
|
|
|
+
|
|
|
+ return strings.Join(newlineSplit, "\n")
|
|
|
+}
|